-
Notifications
You must be signed in to change notification settings - Fork 866
Exercise Markup
Exercises are specified entirely using HTML and are contained within a simple HTML page. You can specify a series of problems (which contain a problem introduction, a question, and a valid solution). Additionally you can specify a series of hints that will be provided to the user upon request.
Exercises are designed to be contained within a single HTML file. The basic layout for a single, simple exercise can be found below.
<!DOCTYPE html>
<html data-require="math">
<head>
<title>Name of Your Exercise</title>
<script src="../khan-exercise.js"></script>
</head>
<body>
<div class="exercise">
<div class="vars">
<!-- Your variables in here... -->
</div>
<div class="problems">
<div>
<p class="problem"><!-- An overview of the problem. --></p>
<p class="question"><!-- The question to ask the student. --></p>
<p class="solution"><!-- The correct answer expected of the student. --></p>
</div>
</div>
<div class="hints">
<!-- Any hints to show to the student. -->
</div>
</div>
</body>
</html>
You can copy this markup into an HTML file and start building an exercise right away.
Depending upon the type of exercise that you're writing you'll likely need to load one or more utility modules in order to render the exercise correctly. This can be done by specifying a data-require="..."
attribute on the HTML element of the page.
There are an ever-increasing number of modules (you can find them in the utils/
directory) but the ones that you'll most-likely need are: math
(for math-related formulas and utility methods) and graph
(for rendering graphs and charts).
Most mathematical problems that you generate will have some bit of random-ness to them (in order to make for an interesting, not-identical, problem). You can generate these values up-front so that you can re-use them again in your problems.
To start you'll want to populate some variables inside the <div class="vars">...</div>
. Variables are typically defined by using a <var>...</var>
element. You'll want to specify an ID for the var as that'll be the name that you'll refer to in the future. The content of a <var>...</var>
block will be executed as JavaScript, with access to to all the properties and methods provided by the JavaScript Math object, as well as all the properties and methods defined in the modules which you have included.
For example to make a variable named SPEED1
that is a number from 11 to 20 you would do:
<!-- Random number 11 to 20. -->
<var id="SPEED1">11 + rand(9)</var>
Since the content of <var>...</var>
blocks is executed as JavaScript, you also have full access to the regular syntax of JavaScript, so you can do something slightly more complicated:
<!-- Random number -10 to -1, 1 to 10. -->
<var id="A">(random() > 0.5 ? -1 : 1) * (rand(9) + 1)</var>
If your goal is to generate a random number, there are various options; you can use Math's random()
, or one of the following methods defined in the math.js
module (which should be included in all exercises):
-
rand(num)
- Get a random integer in [0, num). -
randRange(min, max, count)
- Get a random integer betweenmin
andmax
, inclusive. Ifcount
is specified, will return an array of random integers in the range. -
randRangeWeighted(min, max, target, perc)
- Get a random integer betweenmin
andmax
, inclusive, with aperc
chance of hittingtarget
(which may or may not be in the range). -
randRangeExclude(min, max, excludes)
- Get a random integer betweenmin
andmax
that is never any of the values in theexcludes
array. -
randRangeNonZero(min, max)
- Get a random integer betweenmin
andmax
that is never zero. -
randFromArray(arr)
- Get a random member ofarr
.
Inside of a <var>...</var>
you can refer to other variables that you've already defined (order matters) by simply referencing their name. For example in the following we define two variables (AVG
and TIME
) and then multiply them together and store them in a third variable (DIST
).
<!-- Compute one value based upon two others. -->
<var id="AVG">31 + rand(9)</var>
<var id="TIME">1 + rand(9)</var>
<var id="DIST">AVG * TIME</var>
Another common use case is for when you want to pick a random word. To do this you would use an unordered list instead of a var element.
<ul id="VEHICLE1">
<li>bike</li>
<li>bus</li>
<li>camel</li>
<li>elephant</li>
</ul>
This will give you a variable VEHICLE1
that will contain one of the previously-specified words.
If you want to make sure certain conditions are met when choosing variables, add a data-ensure
attribute to the vars block. For example, if you want to choose two variables such that the first is greater than the second, you can write:
<div class="vars" data-ensure="A > B">
<var id="A">randRange(1, 10)</div>
<var id="B">randRange(1, 10)</div>
</div>
If the condition in the data-ensure
block evaluates to false, the entire block will be re-evaluated until the data-ensure
condition evaluates to true. Make sure the condition does not fail too often; if it does, then the variables will have to be re-chosen many times, slowing down the browser, and this is usually a sign that the variables could have been chosen more efficiently.
If only a certain subset of variables need to meet a condition, then you can use a data-ensure
on a subset of variables with a div
element.
<div class="vars">
<var id="A">randRange(1, 10)</var>
<div data-ensure="B < C">
<var id="B">randRange(1, 10)</var>
<var id="C">randRange(1, 10)</var>
</div>
</div>
This is preferable to placing a data-ensure
on the entire vars block when possible, since only the necessary variables will be re-evaluated.
There may also be cases where you want to make sure a certain condition is met when choosing a single variable; in this case, add a data-ensure
attribute to the individual var
element. For example, if you want to make sure two variables have different values, you can write:
<div class="vars">
<var id="A">randRange(1, 10)</var>
<var id="B" data-ensure="A !== B">randRange(1, 10)</var>
</div>
If you can, place data-ensure
s as deep into the tree of elements as you can; that is, avoid placing data-ensure
s on large groups of variables if it's not necessary. Not all behavior can be achieved by placing data-ensure
s on single variables (for example, choosing A and B such that A < B will produced a skewed distribution if you only place the condition only when choosing B). The idea is to have no "wasted" computation inside data-ensure
s, since all thoes computations must be repeated each time the data-ensure
fails.
Problems are a collection of a problem overview (or introduction), a question, and a solution. All problems are contained within an element with a class of "problems".
The basic structure of a problem looks something like this:
<div class="problems">
<div>
<div class="problem"><!-- An overview of the problem. --></div>
<p class="question"><!-- The question to ask the student. --></p>
<p class="solution"><!-- The correct answer expected of the student. --></p>
</div>
</div>
</div>
The exact elements that you use aren't important (they could be divs or paragraphs or something else entirely); all that matters is that they have the proper classes ("problem", "question", and "solution").
When defining the various parts of the problem, it will be necessary to refer to refer to the variables you defined in the vars block. When defining the introduction, question, and solution, you can always refer to previously-defined variables with a <var></var>
block; for the full list of formatting options (which are all available when defining the introduction and question), see the Formatting section of this wiki.
The problem overview/introduction is optional and is defined with a class of "problem." It is mainly useful for word problems or any kind of problem that has important text or information that isn't explicitly part of the statement of the question. For example, a Physics problem may describe the situation and the various objects in the world before asking about a certain quality of a certain object.
The question is required and is defined with a class of "question". At the moment, the question is appended directly after the problem and is fromatted the same, although this may change depending on the system.
We provide these guidelines for how to choose the problem overview content and question content, but they are ultimately left at the discrution of the exercise author. The contents are not handled or interpreted in any particular way.
Unlike problem introductions and questions, solutions do have a very specific form of markup. Like introductions and questions, it is the class name ("solution") which matters, not the element type; however, the content of the solution element is very important and is used as the basis for validating the user's input.
For example, a valid solution that is dynamically-generated from a previously-created variable would be:
<p class="solution"><var>round(DIST1)</var></p>
The user would then need to enter a number into the fill-in-the-blank form that matches the DIST1
variable, rounded to the nearest integer.
There are a few answer types available. You can specify what type of answer is expected by using the data-type
attribute on the .solution
element.
-
text (default)
This is the default form. It compares the answer strings literally; no numerical interpretation is done at all (e.g.,
1.0
would not be marked correct if the answer was1
, even though they're numerically equal). The only text transformation is to strip leading and trailing whitespace before comparison. -
radio (default if
ul.choices
present)If you want the user to pick an answer from a list, you can provide the possible choices that you want the user to select from, using an un-ordered list. You'll need to add an UL with a class of
choices
inside of your main problem container.<p class="solution"><code><var>A</var>x <var>BP + B</var></code></p> <ul class="choices"> <li><code><var>-1 * A</var>x <var>BP + B</var></code></li> <li><code><var>A</var>x <var>BN + (-1 * B)</var></code></li> <li><code><var>B</var>x <var>AP + A</var></code></li> <li><code><var>-1 * B</var>x <var>AN + (-1 * A)</var></code></li> </ul>
The above markup will generate a problem that has 5 possible choices to choose from, one of which is the valid solution. Note that when you use multiple choice formatting you can let the user pick from more-complicated answers (such as formulas).
The framework also gives you the ability to provide more possible choices than what may actually be displayed. For example you could provide a total of 5 choices (1 solution + 4 other choices) but only show 3 of them (1 of which will always be the valid answer).
<ul class="choices" data-show="3"> ... </ul>
Additionally the framework provides a mechanism for supplying a "None of the above" choice as a viable option. All you would need to do is supply a
data-none="true"
attribute on your choices UL to enable it, like so:<ul class="choices" data-show="5" data-none="true"> ... </ul>
In this example, 5 possible choices will be displayed - one of which will be the "None of the above" choice. It's possible that the valid solution will be hidden and that "None of the above" will be the correct answer.
-
category (a type of radio)
If your exercise involves asking the user to choose from the same set of choices displayed in the same order every time, supply a
data-category="true"
attribute. Note that you should supply the solution in the unordered list as well (Normal radio answers only have wrong answers in the unordered list.).<div class="solution">Acute</div> <ul class="choices" data-category="true"> <li>Acute</li> <li>Right</li> <li>Obtuse</li> </ul>
-
decimal
Just like text except it compares numerical equality. With
decimal
,1.0
is the same as1
is the same as1
, but no other evaluation is done so fractions and the like don't work.<p class="solution" data-type="decimal">1.0</p>
-
rational
Compares two fractions for equality. Under the hood, it converts both to decimals so the contents of
p.solution
should actually be a decimal version of the correct answer. You can accomplish this usingvar
:<p class="solution" data-type="rational"><var>2/3</var></p>
By default, answers must be reduced to be considered correct. (
4/6
would not be a correct answer.) If you want to specifically allow unsimplified answers, adddata-simplify="optional"
to the element containing the solution. -
radical
Use this type if your answer involves a radical, like in the Simplifying Radicals exercise. Specify the coefficient and the part under the radical in separate
span
tags.<div class="solution" data-type="radical"> <span><var>COEFFICIENT</var></span><span><var>RADICAL</var></span> </div>
-
regex
Just what it sounds like. Omit the delimiting slashes but include
^
and$
if appropriate. To match the four smilies:)
,;)
,:P
,;P
:<p class="solution" data-type="regex">^[:;][\)P]$</p>
-
multiple
This special type allows you to have a composite of other answer forms as an answer. The "Equation of a line" exercise uses this to ask the user for the slope
m
and y-interceptb
of a line:<div class="solution" data-type="multiple"> <p><code>m</code> = <span class="sol" data-type="rational"><var>M</var></span></p> <p><code>b</code> = <span class="sol" data-type="rational"><var>B</var></span></p> </div>
The code inside the solution will be copied into the solution area and each
.sol
element will be interpreted as if it was its own answer. In this case, you'll get a form that asks form
andb
and gives two separate text entry boxes. Sincedata-type="rational"
was specified on each.sol
element, each text box acts like a single rational answer.Answers are marked correct only if all of the parts are correct. Having a radio input as a part of an answer has not been tested. (You probably don't need more than one radio input, anyway.)
Hints are a very important part of exercises. Hints should never be required for a user to complete an exercise; they should only be provided to help students that are stuck. But it is important to carefully think through what hints to display; if a student is stuck and cannot figure out a problem, the hints should be able to provide a fundamental improvement to their understanding of the problem and how to solve it. How hints are integrated with the site will be left up to the framework; as the writer of the exercise, all you need to do is provide a bunch of individual hints.
Hints are contained within a <div class="hints"> ... </div>
block. The markup that you use inside the block is completely at your discretion, but each child of the hints block will be rendered as a separate hint (and each separate hint will be displayed one-at-a-time when the user clicks the "Hints" button). For example, in the following code:
<div class="hints">
<p>Remember that <code>d = r * t</code>, or written another way, <code>t = d / r</code></p>
<div>
<p><code>d_<var>V1</var> =</code> distance that Alice traveled by <var>VEHICLE1</var></p>
<p><code>d_<var>V2</var> =</code> distance that Alice traveled by <var>VEHICLE2</var></p>
<p>Total distance: <code class="hint_orange">d_<var>V1</var> + d_<var>V2</var> = <var>DIST</var></code></p>
</div>
<p>Total time: <code class="hint_blue">t_<var>V1</var> + t_<var>V2</var> = <var>TIME</var></code></p>
...
</div>
The first hint will be the "Remember that..." paragraph; the second hint will be the 3 inner paragraphs, and the third hint will be the "Total time: ..." paragraph.
The last hint of every problem should be the problem's answer. The hints should guide the user to this answer.
It may also be useful to display a hint, and then replace certain values or append contents to it later. In these cases, you can make use of hint template inheritance. For example, if you want to replace a value, you could do something like this:
<div class="hints">
...
<p><code><span id="expand">?</span>^2=25</code></p>
<span id="expand" data-apply="hint-replace">\color{blue}{5}</span>
...
</div>
In this example, the first hint will display "?^2=25". Then, once the user hits the "Get hint" button again, the span
containing "5" will replace the span
containing "?". The template inheritance mechanism is triggered because the two span
elements share the same id. With normal template inheritance (ie, if the span
had used data-apply="replace"
), the second span
would replace the first before anything is displayed to the page; the result would just be one hint that says "5^2=25". Since we specified hint template inheritance, the template inheritance won't be triggered until the triggering element is displayed by hitting the "Get hint" button. Hint template inheritance works exactly like normal template inheritance, except that it will not be applied until the hint is shown. For a full list of template inheritance methods, see the section on Template Inheritance in Formatting; the corresponding hint template inheritance methods can be used by just prepending "hint-" on to the name (ie, "append" becomes "hint-append", "appendContents" becomes "hint-appendContents", etc.)
Note that in most cases it is beneficial to display separate hints, so the user has a "trail" of hints to look at and understand the thought process. The goal is for a user who doesn't understand the problem to be able see the trail of hints and understand better how to try the next problem.
Color is advised when modifying previously-displayed hints through hint template inheritance; it will make it clear to the user that a hint has been updated.
You can color various parts of your hints by using provided CSS classes. The provided classes are hint_blue
, hint_red
, hint_orange
, hint_gray
, and hint_green
.
While it's totally possible that you might create an exercise with a single type of problem, it's very likely that you'll want to provide students with multiple styles of problems to challenge them. For example one problem could ask for total distance travelled, another could ask for how long it took the travel the distance, etc. We would like exercises to be as modular as possible, so consider creating multiple separate exercises unless the problems you are considering are fundamentally very similar.
Thankfully you won't have to re-write the entire problem from scratch, you'll only have to write the new portions of the problem that differ from the original. The way you do this is by adding a unique ID to one of your problems and then referencing it from subsequent problems using a data-type="ID"
attribute.
For example in the following markup we create two types of problems. One is the base, core, problem (with the ID of "original") and the other is the problem that inherits from the original.
<div id="original">
<div class="problem">
<p>Alice traveled by <var>VEHICLE1</var> at an average speed of <var>SPEED1</var> miles per hour.</p>
<p>Then, she traveled by <var>VEHICLE2</var> at an average speed of <var>SPEED2</var> miles per hour.</p>
<p>In total, she traveled <var>DIST</var> miles for <var>TIME</var> hour<var>TIME > 1 ? "s" : ""</var>.</p>
</div>
<p class="question">How many miles did Alice travel by <var>VEHICLE1</var>? (Round to the nearest mile.)</p>
<p class="solution"><var>round(DIST1)</var></p>
</div>
<div data-type="original">
<p class="question">How many miles did Alice travel by <var>VEHICLE2</var>? (Round to the nearest mile.)</p>
<p class="solution"><var>round(DIST2)</var></p>
</div>
Note how the second problem doesn't provide a problem definition. This problem definition is inherited directly from the original problem. Any markup provided by a subsequent problem will override the original. For example providing a "question" in a follow-up problem will override the "question" coming from the original.
Using this technique you can easily generate many different styles of problems with only minimal amounts of typing.
If you want to have one problem come up more frequently than another, add a data-weight
attribute to its containing <div>
:
<div id="original" data-weight="3">
...
</div>
<div data-type="original">
...
</div>
In this example, the first problem will appear 3 times as often as the second.
If the different problems you are considering have different variables, then you can declare variables individually for each problem. You can also declare any shared variables globally, and only define the ones that differ in each problem. For example, an exercise using word problems will probably require very different variables for each problem type. Here is the structure that would be used to define problem-specific variables:
<div class="vars">
<!-- global variables used by all problem types -->
<!-- this div is OPTIONAL -->
</div>
<div class="problems">
<div id="original">
<div class="vars">
<!-- variables specific to problem "original" -->
</div>
...
</div>
<div id="second">
<div class="vars">
<!-- variables specific to problem "second" -->
</div>
...
</div>
</div>
You could also attach a data-apply="original"
attribute to the second problem, and then all the variables declared in the first problem would be accessible in the second problem.
If you wish to provide hints that are specific to the problem that the user is working on (rather than generic to the overall problem). You can do this by providing hints within a problem itself. These hints will override hints in the base "hints" block.
If you would like separate hints for each problem, you can leave out any generic/base "hints" block, and define all your hints in the individual problems.
If there are both generic and problem-specific hints, the class-based inheritance system kicks in. For example, in this particular exercise there is a hint block that contains two placeholders: "hint1" and "hint2". These placeholders do not contain any hints and will be populated later by specific problems.
<div class="hints">
<p>Let's break this problem into smaller and easier pieces.</p>
<p class="hint1"></p>
<p><code><var>A</var> * x = </code><code class="hint_orange"><var>A</var>x</code></p>
<p class="hint2"></p>
<p><code class="hint_orange"><var>A</var>x</code><code class="hint_blue"> + <var>B</var></code></p>
<p>So, the original phrase can be written as <code><var>A</var>x + <var>B</var></code></p>
</div>
Inside a problem the author may write something like the following:
<div data-type="original">
...
<div class="hints">
<p class="hint1">What is <span class="hint_orange">the product of <var>A</var> and x</span>?</p>
<p class="hint2">What is <span class="hint_blue">the sum of <var>B</var></span> and <code class="hint_orange"><var>A</var>x</code>?</p>
</div>
</div>
The framework will take the above markup contained within the <div class="hints"> ... </div>
, go through each of the child elements (in this case, the paragraphs), and replace the associated paragraphs in the main "hints" block (thus "hint1" will replace "hint1", "hint2" will replace "hint2" and so on). What class names you wish to use can be completely at your discretion.
At any point in your problem intros, questions, solutions, or hints you can reference any variable that you've previously defined using the same syntax as before. Within a .vars
block, the <var>...</var>
tag is used to define variables; outside of a .vars
block, the tag is used to refer to previously-defined variables. For example, if you wanted to mention a vehicle and speed in your problem you can do:
<p>Alice traveled by <var>VEHICLE1</var> at an average speed of <var>SPEED1</var> miles per hour.</p>
And the variables will be substituted as you would expect them to be.
While displaying single variables can be useful, it's often that you'll want to display entire formulas using mathematical notation. You can define your mathematical notation using LaTeX. The full list of available commands can be found here:
A full book explaining all the commands and how to write Math using LaTeX can be found here:
Inside the Khan Exercise markup we use a <code>...</code>
block to delineate where a formula will go. For example the following code block will be hidden and replaced with a MathJax-rendered formula.
<code>x^2</code>
Naturally you can place <var>...</var>
blocks inside of code blocks, giving you a nicely-rendered final result, for example:
<code><var>A</var>x + <var>B</var></code>
Additionally you can feel free to provide class names on the code block itself. Those classes will be moved to a span and will be wrapped around the final MathJax-generated formula. For example:
<code class="hint_orange"><var>A</var>x</code> <code class="hint_blue">+ <var>B</var></code>
You can feel free to put formulas in your problems, questions, hints, solution, and even multiple choices.
WARNING: This API is very much in flux and is still being defined, use with caution.
It's possible to include graphs in your problems, hints, and solution/answers. A basic graph is defined with a class
of graph
. Width and height are specified as CSS in a style
attribute like so:
<div class="graph" style="width: 200px; height: 200px;">
<!-- Graph-related commands here. -->
</div>
You can then use graph-related commands to do rendering. At the moment the exact API for rendering is very much in flux. We're currently using a variation of the ASCIIsvg API but are looking to switch to something better.
For a current list of commands see the following:
http://www1.chapman.edu/~jipsen/svg/asciisvgcommands.html
If you wish to set the styling for how drawing should look you can currently do this in the data-style
attribute on the graph element. For example:
<div class="graph" style="width: 400px; height: 400px;"
data-style="font-size: 15px; font-family: sans-serif; font-style: bold; stroke: blue;">
</div>
Additionally there are built-in styles of graphs. At the moment the only built-in style is "plane" (which gives you a -10 - 10 x/y coordinate grid). This can be set using the data-graph-type
attribute.
An example full graph definition is as follows:
<div class="graph" style="width: 400px; height: 400px;"
data-style="font-size: 15px; font-family: sans-serif; font-style: bold; stroke: blue;"
data-graph-type="plane">
plot('(' + quadratic + line + ')/' + line);
ASdot([a, limtoa], 4, "black", "white");
</div>
The exercise framework also includes some basic templating support as well.
To use conditional templating, attach appropriate data-if
, data-else-if
, and/or data-else
attributes to your blocks like so:
<p data-if="NUM === 2">The number is 2!</p>
<p data-else-if="NUM === 1">The number is 1!</p>
<p data-else>The number isn't 1 or 2!</p>
If the condition included with a data-if
tag evaluates to true
, then the element will be included in the DOM; otherwise, the element will be removed before the page is rendered.
Elements with data-else-if
or data-else
attributes must have older (ie, preceding) siblings with data-if
or data-else-if
attributes; basically, a data-else-if
or data-else
needs to be be preceeded immediately by a data-if
or a data-else-if
for the construct to make any sense.
You can also nest data-if
blocks and do more interesting things.
<div data-if="NUM1 === 2">
<div data-if="NUM2 === 2">Both numbers are 2!</div>
<div data-else>The first number is 2, but the second is not.</div>
</div>