DML Advanced Topics

This tutorial covers advanced topics in the Dynamic Markup Language. If you have read through the first few tutorials and haven't experimented by writing your own code yet, now would be a good time to write a few basic DML scripts before continuing.

Argument Parsing

As stated earlier in this document, DML scripts can be given arguments which you can use to affect the behaviour and results of a running script. This next example demonstrates a script which is used to create close gadgets - i.e. a button that can destroy windows on the desktop. This is our first script to serve a real purpose in a desktop interface, so it's important that we support arguments to make the script as flexible as possible.

  <!-- Arguments -->
  <args name="Gadget_Close" x="?" y="?" width="16" height="17"/>
  <args object="?"/>

  <!-- Create the Close Gadget -->
  <render name="{name}" sticky width="{width}" height="{height}"
     x="{x}" y="{y}">

    <onclick>
      <action static call="free" object="[{Object}]"/>
    </onclick>

    <box colour="128,128,128" shadow="64,64,64" highlight="255,255,255"
      raised thickness="1"/>
    <box x="4" y="4" width="=[{name}.width]-8" height="=[{name}.height]-8"
      border="0,0,0" colour="255,255,255"/>
  </render>

In order to define the arguments that our script will accept, we used the <args> tag. Following this is a list of the names of these arguments, and the values that they will default to. In order to use them in our script, we can refer to them using the {arg} convention, which simply involves enclosing the name of a valid argument in curly brackets. Our sixth line of the script which sets up the render object, would translate to the following if default arguments are used:

  <render name="Gadget_Close" sticky width="16" height="17" x="?" y="?">

You will notice that the X and Y arguments default to a question mark, which is unusual given that only a number would be relevant for those attributes. The use of a single question mark has a special meaning in DML when used as an attribute setting. Essentially it acts as a 'plug' to stop a field being set if an argument has gone unspecified. In our example, the DML parser will detect the two question marks for the X and Y fields and ignore them completely, thus leaving the fields unset. This can be very useful if we want accept the object's default values when an argument has gone unspecified.

There is only one thing that you must watch out for when using arguments: The names that you choose for each argument must be consistent with other scripts in the system. For example, an argument for an x coordinate should always be called 'x', as opposed to 'XCoordinate' or 'XPosition' for example. Keeping your scripts consistent will prevent unexpected problems cropping up for yourself and others in the long run.

Calling a Script

Now that we've created a script that can be used for creating close gadgets, our next example illustrates how you would call this script to create a new close gadget. For our purposes, we will assume that the close gadget script was saved to Athene's templates directory as "close.dml".

  <render name="main" width="300" height="400">
    <script src="templates:close.dml" &width="30" &height="30"
      &object="main"/>
  </render>

It's as simple as it looks - just use the <script> tag, specify the file location, and then list the arguments and their settings for the script. You'll notice that we didn't specify all of the arguments - it is only necessary to use the ones that you are interested in - the rest will remain at their default settings.

Storing and Retrieving Variables

Storing variables such as number and string types is a necessity for complex programming, but is generally unavailable in markup languages due to their emphasis on producing documents. As we've established, DML's object oriented approach allows us to push the boundaries a little further, and part of this includes support for variable storage.

Variables are also treated in an object based manner, so a <variable> tag is provided for the purpose of declaring new variables. A typical use of this tag could be as follows:

  <variable name="GlobalVars"
    &Width="10" &Height="20"
    &Hello="World"
    &Pi="3.14"/>

One of the unusual features that you will notice is that there are no type specifications (e.g. integer, string, float). This is because the DML engine will translate variables to the correct type as and when you use them.

It is important to note that the variables are stored in an actual system object, and giving that object a name (e.g. "GlobalVars") is a necessity for further referral. Reading a variable is as simple as using the object referral standard, for example:

  <render width="[GlobalVars.Width]" height="[GlobalVars.Height]"/>

This is simple enough, but setting the variables requires use of another tag known as <set>. The set tag is widely used for a variety of purposes, and can be used to write data back to any object as well as variable stores. If we wanted to multiply the width variable by 10 and write it back to the variable store, we would use this tag:

  <set object="GlobalVars" &Width="=[GlobalVars.Width]*10"/>

Variables can also be created on the fly. If we wanted to calculate the area and write it back to the variable store, we can do this:

  <set object="GlobalVars" &Area="=[GlobalVars.Width]*[GlobalVars.Height]"/>

Even though we never specified an Area variable to be created, the variable class is smart enough to create the variable for us.

Decision Statements

Surprisingly enough, DML's object based approach has allowed us to introduce if/endif statements which are common in non-markup languages. "If" statements can also be executed in two different modes - they can be resolved at script processing time, or constantly resolved during run time processing.

Dealing with the first instance, we will create a script that makes a decision based on an argument that has been passed to it. If the condition is satisfied then we will display a text string, otherwise no text is displayed:

  <args displaytext="No"/>

  <render width="500" height="30" colour="100,0,0">
    <if statement="{displaytext} = Yes">
      <text x="3" y="3" string="Hello World"/>
    </if>
  </render>

As you can see, the core of the if statement was specified in the statement field. While it is slightly long-winded, the principle is the same as most 3GL languages, and it can be seen that the if statement roughly translates to the following:

  if {displaytext} = Yes then
     print "Hello World"
  endif

The condition that we used in the above statement was the equal sign - other supported conditions include the standard "<>", "!=", "<=", ">", "<", "==", and ">=" signs.

Whenever an if statement fails, everything that is enclosed by the <if> tag will be ignored by the DML parser. Thus, regardless of how much content you place inside of the if block, the tags will always be ignored in the event of a statement failure. If the statement succeeds, then the internal tags will be processed as normal. If the situation calls for an else statement, these are also supported - an example is given in the next sub-section.

Checking for Object Existence

A special feature included with if statements is the ability to check if a particular object exists in the system. This feature is often used in situations where there is chance that the creation of an object may fail due to "external circumstances". A good example is the use of the image tag, which requires that you specify the location of an image file to be loaded. If the image file cannot be found, the image object will fail and the object will not be created. If the rest of your script has a dependency on the loading of the image, this could cause some major problems and unpredictable results. The solution is to use some foresight and check that the image was loaded successfully before executing the rest of the script. Here is an example:

  <image name="logo" src="pictures:logo.jpg"/>
  <if exists="logo">
    ...
  </if>
  <else>
    ...
  </else>

All that is necessary is that the object that we want to check for has been given a name. The rest is just a matter of using an if statement and the exists attribute to inform the DML parser that we want to check for the existence of an object. If the object is found to exist, then everything inside the if tag will be executed. If the object does not exist, we can use a "backup plan" inside our else statement, and print an error for the user or apply a satisfactory work-around.

Repeating Sections

Simple loops are supported in DML via the <repeat> tag. Conditional loop constructs such as for or while statements are not supported, but are likely appear in a future revision.

Use of repeat statements is as straight-forward as demonstrated in the following example, which repeats a section of DML ten times. You will notice that everything that we want to repeat is encapsulated by the repeat tags, and that the current loop index can be referred to via the {index} argument. In all cases the index will start at zero and stop at the given count minus one (in this case, nine).

  <repeat count="10">
    <line x0="={index}*20" y0="0" x1="100" y1="100" colour="#000000"/>
  </repeat>

It is not possible to break out of repeating loops, but if statements can be used to the same effect if a situation requires a loop to stop execution before the index reaches the maximum count.

Halting an Object Script

On occasion, you may find it necessary to temporarily halt script processing so that the user can have a chance to respond or interact with your script. Under normal circumstances a DML script will execute at maximum speed in an attempt to go from start to finish as quickly as possible. If you need to pop-up a confirmation dialog box or ask the user to make a decision that will affect further processing, you will have to tell the system to wait for that response. If you forget to wait, any input box that you pop-up will go unanswered until the script is completed and subsequently destroyed.

To wait for a response, use the <wait> tag just after creating the window that requires a response. You need to tell the system what object you are waiting for and what action you are interested in responding to. In our case we will be waiting for our window to receive the Free action, which is executed when the user either closes the window or presses a confirmation button. Here is an example that creates a fully functional dialog box and then waits for a user response at the end:

  <!-- Dialog box -->

  <window name="win{id}" center insidewidth="240" insideheight="60"
    minheight="60" minwidth="240" title="Confirmation Required"
    insideborder="0" sticktofront>

    <text face="[gl_fonts.window_face]" colour="[gl_fonts.window_colour]"
      align="horizontal" y="=[container.topmargin]+4"
      string="Are you sure?"/>

    <button text="Yes" x="[container.leftmargin]" xoffset="51%" height="0"
      yoffset="[container.bottommargin]">
      <action static call="free" object="[win{id}]"/>
    </button>

    <button text="No" x="51%" height="0" xoffset="[container.rightmargin]"
      yoffset="[container.bottommargin]">
      <action static call="free" object="[win{id}]"/>
    </button>
  </window>

  <!-- Do not proceed until the user has responded to our dialog box -->

  <wait object="[win{id}]" action="free"/>

Processing stops as soon as the wait tag is executed. When the user has responded to the window, processing will continue and if necessary we can take action based on the user's response.

Statically Bound Objects

There are two different types of objects that can be created in DML scripts - those that perform a particular action and then exit, and those that perform a service on a regular or irregular basis.

Take the <set> tag and compare it to the use of the <render> tag for example. Whenever a render tag is executed, a graphical object is permanently created which resides on the screen display. It continues to "stay alive" well after the script has terminated, until it is explicitly removed from the system. In this case, the render object is referred to as being "static". On the other hand, using the <set> tag results in an object which performs the necessary activity and then removes itself once the closing tag, </set>, is received. This is called a "non-static" object.

Being aware of this is extremely important, as you will often need non-static objects to defer their execution, rather than activate while the script is being parsed. Use of the <script> and <if> tags are primary examples of this. In the following example, we want to create a button which responds to the user by executing a script. In order to prevent the script from executing while the DML parser is doing its processing, we declare it as "static" so that it activates only when the rendered area is clicked by the user:

  <render width="400" height="400" colour="255,255,255">
    <render x="20" y="20" width="60" height="60" colour="0,0,255">
      <onclick>
        <script static src="demos:starfield.dml"/>
      </onclick>
    </render>
  </render>

If we made the mistake of leaving out the static keyword from the script, the "demos:starfield.dml" script would execute immediately, completely bypassing the onclick object and causing us plenty of confusion.

This next example uses the same case, but this time we want to use the <if> tag to make a decision when the user clicks on our rendered object:

  <render width="400" height="400" colour="255,255,255">
    <render name="button" x="20" y="20" width="60" height="60" colour="0,0,255">
      <onclick>
        <if static test="[button.x]" condition="=" compare="20">
          <set static object="button" &x="40"/>
        </if>

        <if static test="[button.x]" condition="!=" compare="20">
          <set static object="button" &x="20"/>
        </if>
      </onclick>
    </render>
  </render>

The above script will cause our button to change position whenever the user clicks on it. You'll notice that we had to use the static keyword four times to achieve this, so once again you can see the importance of using this keyword correctly.

You will find that not all objects support the static keyword - in fact most of them don't need it - but those that do will always document the fact. Given practice, usage of this keyword will become second nature as you learn more about the available objects and how they function.

Pre-defined Arguments

In Section 3.3 we discussed arguments and how to reference them. This next section elaborates on a few special arguments that are pre-defined for all scripts when they are processed. All pre-defined argument names are reserved, but if you use a reserved name in an <args> definition you will over-ride its pre-defined equivalent. The currently available pre-defined arguments are as follows:

{Container} - Every script that is executed is assigned a container, an object that serves as the 'root' that all objects in the script will belong to. If you need to refer to the object ID of the container, do so through this field.

{ID} - Each script is given a unique ID when executed. Referencing the ID through this argument can be useful in any situation which requires a genuinely unique number. The ID field is most commonly used in the naming of objects, as a means of ensuring that an object name is not a duplicate of another.

{Output} - If a script is developed with the intention of sending printed output to the user, the Output argument will need to be referenced to discover the object that is providing output services. This is similar to referencing 'stdout' in a POSIX compliant system. The Print tag is commonly used in conjunction with the Output argument when text printing is required.

{ScriptDir} - If a situation requires you to know the directory that a script was loaded from, you can use this argument to find out where it is. The directory string is fully qualified, which means it will end with either a slash or colon character (e.g. "scripts:templates/").

{ScriptLocation} - Similar to the ScriptDir argument, you can find out the exact file location of a script by referencing this field.

Adding New Tags to the DML Instruction Set

Being a dynamic language, the available "instruction set" used by DML is entirely dependent on what is and isn't installed on your particular system. If in the event that a script doesn't work due to a missing class, getting that script to work is as simple as obtaining that particular class and installing it to the correct 'system:classes/' directory used by Athene. All registered classes are available in the standard Athene archive, so extra classes will generally come from third parties.

If you want to write a new class for use in DML scripts, then you must download the Pandora Engine SDK and read the supplied documents on the creation of new classes. The most important decision that you will need to make is whether or not you want to develop an officially recognised class for public use, or a private class for internal or commercial use. Public classes take a little more work, but the results are more rewarding as they are used by a wider audience.

Writing a new class can take as little as an hour, to a few days or weeks depending on its complexity. Other factors such as documentation and testing add to this, so the average time to develop a sufficiently matured class is about 4-6 weeks. For more details on class development, please refer to the Pandora Engine SDK.

  1. Introduction
  2. Basic Graphics
  3. User Interaction
  4. Advanced Graphics
  5. Templates
  6. Advanced Topics

About Us | News | Downloads | Athene  |  SDK | DML | Forums | Site Index

Copyright Rocklyte Ltd © 2002-2007.