Tutorials📘 Introductory Tutorial10. Doenet Authoring Advice (0.5 hr)

Doenet Authoring Advice

Although we’ve only scratched the surface of DoenetML features, at this point you already know enough to write some fairly complex documents. However, complex documents can lead to some subtle, hard-to-find bugs in your code. Hence this seems like a good time to take a step back, talk about Doenet’s general philosophy towards errors, and give some other advice to help your authoring and debugging efforts.

General Philosophy

Doenet’s general philosphy is to crash with an error message as rarely as possible. Even if you make a mistake or omission in your code, Doenet will do its best to render something on the screen, even if it’s not what you originally intended.

At a basic level, this often means Doenet objects have default values chosen for their options. In some languages, for example, you can’t create a slider without specifying the range of values and the step size. In DoenetML, you can omit one or more of those options from a <slider/> tag, and Doenet will still display a slider. It just might not be the one you originally intended!

More generally, there are some fairly serious errors that would immediately cause a crash with other languages, but not Doenet — although the results may look odd at times! Consider the following short example,

<math>$x^2</math>

Here, we define a <math> object whose value is the square of $x… except xx isn’t defined in the document! As seen in an earlier example, the result is _^2, which is Doenet’s way of saying “there’s nothing to square here, so I’ll just render a blank/unknown value raised to the second power.”

Along the same lines: if you give Doenet an invalid attribute value, it will generally just ignore the attribute instead of crashing with an error message. In the following code, we made a typo and used a curly bracket in the domain, writing [0,2}[0,2\} instead of [0,2][0,2]. Notice that Doenet still defines the function, and does not restrict its domain at all; f(1)f(-1) and f(3)f(3) are both defined.

<function name="f" domain="[0,2}">
  x^2
</function>
 
<p>$$f(-1)</p>
<p>$$f(0)</p>
<p>$$f(1)</p>
<p>$$f(2)</p>
<p>$$f(3)</p>

Test code here.

On one hand this is very forgiving and helpful, but it can be surprising as well. Suppose you notice that f(10)f(10) is defined even though the domain of ff is supposed to be [0,2][0,2]. You might think there’s a bug in Doenet’s code regarding domains, but in fact (at least in this case) it’s author error. For this example, Doenet will issue several warnings that look like this : Line #5 Insufficient dimensions for domain for function. Domain has 0 intervals but the function has 1 input.

(Note - to view warnings look for the yellow button in the bottom left corner of the text editor.)

Avoiding Crashes

There are relatively few reasons why a Doenet document will fail to load, and display an error message instead. The most common are:

  • Omitting a closing tag. If you omit a closing </p>, for example, Doenet will give you an error message similar to

    Error: Invalid DoenetML. Missing closing tag. Expected </p>. Found on line 8

    The displayed line number tells you the location of the error in the document.

  • Invalid tag name. Doenet currently has no <integrate> tag, for example; if you tried to integrate a function with the code <integrate>$f</integrate>, Doenet will give you an error message similar to

    Error: Invalid component type: <integrate>. Found on line 81

  • Invalid component name. If you use the name attribute with a component, you must follow these rules:

    • Names can only contain numbers, letters, and underscores; they cannot contain any other punctuation symbols.

    • Names cannot begin with a number or an underscore.

    Unlike attributes and tags, names are case-sensitive in DoenetML. A name can match the same text as an attribute; for example,<math name="domain">4</math> is valid DoenetML. But we encourage you to avoid this so that you don’t cause any confusion.

  • Duplicate component name. You cannot give two components the same name.

Attributes vs. Children

In DoenetML there are two common ways to modify the definitions or behavior of components. You’re already familiar with attributes, which are included in the opening tag of a component, e.g.

     <function name="f" domain="[0,1]">3x-1</function>.

Children are essentially nested tags. For example, the label in a slider has to be provided inside a <label> tag, like this:

     <slider>
 
       <label>label text</label>
 
     </slider>

The slider label cannot be specified as an attribute; <slider label="Text"/> does not work.

So far you’ve usually worked with attributes, but as you continue on through this tutorial and learn more advanced DoenetML code, children become more common. (That’s especially true when we talk about graphs and multiple choice questions.) Confusing the two is a common bug when writing DoenetML code; if an attribute isn’t working, double check the documentation to see if it should be a child, and vice-versa.

A Note on Self-Closing Tags

Up until now, we’ve made a point of mentioning when a tag is self-closing. For those who are new to HTML-style markeup languages (including DoenetML), it’s worth saying a few extra words about these tags before you continue on to the more advanced sections of this tutorial.

  • Self-closing is an option for some DoenetML tags, but is never required. It might be faster to write <mathInput name="x"/>, but

     <mathInput name="x"><mathInput>

    will work exactly the same. This is why, throughout the tutorial, we’re not particularly careful about whether we refer to “the <mathInput> tag” or “the <mathInput/>” tag. They’re essentially synonyms.

  • Even if a tag can be self-closed, there might be times when you must explicitly write out opening and closing tags instead. To re-use the above example: if you want to add a label to a slider, you cannot use a self-closing <slider/> tag.

As you progress through this tutorial, you’ll run across many more tags which can be self-closing in some cases, but not in others. For example, when we cover graphics: you can create a point, line segment, polygon, or circle using a self-closing tag with attributes, and we’ll do so. To use certain features, however, we might have to write out opening and closing tags. (Often that’s because a feature requires using a nested child instead of an attribute.)

From this point on, we won’t necessarily go out of our way to point out when a tag can be self-closing or not. You can learn from context and the code examples in those sections. But if you’re ever in doubt: remember that you can always write out the opening and closing tags for those elements.

Next Steps

You’re now finished with the “Basic” part of this tutorial, which focuses on laying the groundwork for writing interactive, text-based mathematical documents. In the second part of the tutorial, you’ll learn how to create graphics in DoenetML, as well as other advanced features: randomization, more user interaction, and so on.