
Purpose and goals
Templates can be used as a method of hiding advanced functionality behind a simple interface, or as a way of managing
frequently used definitions in one centralized location, allowing the user to avoid creating multiple similar definitions and being able
to make changes to one centralized location.
Templates currently operate only on window controls.
Interface definition and encapsulation for basic users
Templates allow the creation of custom control types by defining new control type tags, and hiding advanced functionality such as
scripting and making the components easier to use.
The following example illustrates a complex control type based on numberfield. It takes care of monitoring other number
fields and automatically tracking changes in them to update the value in the control. The linking is performed using the custom
<source> definitions.
<linkednumber name="meleeattackbonus" source="attackbonus.melee">
<anchored>
<to>combatframe</to>
<position>insidetop</position>
<offset>-20,100</offset>
<size>
<height>30</height>
</size>
</anchored>
<frame>
<name>bonus</name>
<offset>5,5,5,5</offset>
</frame>
<font>sheetnumber</font>
<displaysign />
<readonly />
<description>
<text>Melee attack</text>
</description>
<!-- Custom fields -->
<source>
<name>attackbonus.base</name>
<op>+</op>
</source>
<source>
<name>abilities.strength.bonus</name>
<op>+</op>
</source>
</linkednumber>
Object orientation support and design for advanced users
Object oriented design provides several benefits, such as being able to divide components into more easily maintained packages and
being able to utilize basic components and to extend them for purposes that are similar to the original without having to start from
scratch.
Templates can be nested (see below) allowing for single inheritance. Taking advantage of proper function overloading requires a
degree of planning, due to the structure of Lua environments in use. The preference has been to make the system as simple as possible
for the beginning scripter, and allowing for more advanced structures with a few special cases.
Defining templates
Templates are defined using the <template>. The template tag must have the "name" attribute defined.
The body of the template definition should start with a tag containing a type of windowcontrol being extended by the template.
This tag should not contain any "name" or "source" attributes. A template name can also be used, in order to create an inherited
template.
The following example defines a template called "checkbox". It is based on genericcontrol and defines default values
for two properties used by the script block.
<template name="checkbox">
<genericcontrol>
<stateicons>
<on>indicator_checkon</on>
<off>indicator_checkoff</off>
</stateicons>
<script file="scripts/template_checkbox.lua" />
</genericcontrol>
</template>
Using templates
To use a template, declare a control as normal, treating the template name as the control type. Define the name for the control
as usual as an attribute to the element tag.
The template inherits any child tags and script blocks from the template. They can be overridden by the implementing control,
useful for making default values available but overridable. See the section "Merge rules" below for more information.
The following declaration creates a control based on the "checkbox" template defined above. It supplements the generic template
by defining a location and tooltip for the control, and overrides the default icons used.
<checkbox name="spontaneous">
<anchored>
<left>
<anchor>left</anchor>
<offset>5</offset>
</left>
<bottom>
<anchor>bottom</anchor>
<offset>-1</offset>
</bottom>
<size>
<width>20</width>
<height>20</height>
</size>
</anchored>
<stateicons>
<on>indicator_casterspontaneous</on>
<off>indicator_casterprep</off>
</stateicons>
<tooltip>
<text>Toggle spontaneous/prepared</text>
</tooltip>
</checkbox>
Merge rules
By default, the template definition and the control definition using the template will have their child tags merged, meaning that any tag present in
one will be present in the end result, including any child tags. If both the control definition and the template have a similar tag with different child tags, the tag
will be created with both children.
For example, observe the template and control in the following example definition.
<template name="mergetest">
<genericcontrol>
<shared>
<templatesubnode>From template</templatesubnode>
</shared>
<fromtemplate>Unique to template</fromtemplate>
</genericcontrol>
</template>
<mergetest>
<shared>
<controlsubnode>From control</controlsubnode>
</shared>
<fromcontrol>Unique to control</fromcontrol>
</mergetest>
The result of the merge is given below.
<genericcontrol>
<shared>
<templatesubnode>From template</templatesubnode>
<controlsubnode>From control</controlsubnode>
</shared>
<fromtemplate>Unique to template</fromtemplate>
<fromcontrol>Unique to control</fromcontrol>
</genericcontrol>
Simply merging is not sufficient for some uses in templates. Therefore, any tag in a template may contain a "mergerule" attribute taking one of the following
values. When a tag in the definition contains multiple children with the same name (i.e. a list of similar child tags), only the topmost is checked when looking
for a merge rule.
-
"merge": Synonymous with the default operation
-
"replace": If the implementing control contains a tag in the same position as this one, removes the tag in the template and all its
children. This is useful if a tag in the definition should contain values for e.g. multiple states as children of a parent tag, and an override should
replace the child values as well as the main tag.
-
"add": Instead of replacing a value as a result of a merge, a tag in the same position as this one is instead created as another
similar tag in the result. This is useful if a template presents a default list of states or values, but the implementing control is allowed to add new
values to the list.
-
"resetandadd": This is similar to "replace", but after one replace, reverts to "add" mode. The result is that a set of similar tags
in an implementing control will override a similar set in the template.
When a template is inherited by another template, care should be taken to confirm that the outer template provides any necessary merge rules as well.
The following example defines a template that accepts a list of source values, defined through the use of <source> tags. The empty tag with the
"resetandadd" definition is required for the implementing control to inherit the "add" rule, necessary for defining a list of sources.
<template name="linkednumber">
<numberfield>
<source mergerule="resetandadd" />
<script file="scripts/template_linkednumber.lua" />
</numberfield>
</template>
Nesting templates
Templates can be nested, meaning that a template is defined with another template as the source type.
Taking merge rules into account, nested templating is rather straightforward as far as the XML definitions go. Achieving some special features such as
function overloading requires special attention to the script block design, however.
Inheritance
A template has it's own script environment, similar to a normal script block in a control. Any control or nesting template derived from a template has
a separate environment for its script block, as well. The indexing in a script block inheriting a template is set up so that all the variables in the template
script block are usable in the script block inheriting the template as well. This means that a control script can simply call a function defined in a template
script block, and it will work. There are two pitfalls, however.
First, the closure of the function called is the environment of the template script block, and it cannot access variables or functions in the inheriting
control's environment. Second, any variable or function defined in the inheriting script block will make similarly named variables in the inherited environment
unavailable.
The self and super variables
To remedy the problems described above, there are two special variables defined in each script block inheriting another.
The variable self always refers to the topmost environment of the control. This is useful as a means for functions in the inner, inherited
script blocks to access variables and functions in the inheriting environment.
The variable super refers to the inherited template script block environment. This is useful for defining functions that overload, i.e.
have the same name and function, as functions in the inherited environment. The overloading function can use super to optionally access items
in the inherited environment, such as the original function.
Note that the environments pointed to by these two variables still implement the same inheritance rules described above. All non-overloaded members in
environments inherited by the target environment are accessible.
Overloading functions
Overloading refers to creating a function in a script block that has the same name and functionality as a similar function in an inherited script block.
The actual technical definition of the term might be considered to comprise other meaning as well, but the design methodology is useful for inherited template
scripts as well.
An example of overloading follows. The two functions defined here are used to calculate the value of a field with several sources.
function sourceUpdate()
if self.onSourceUpdate then
self.onSourceUpdate();
end
end
function onSourceUpdate(source)
setValue(calculateSources());
end
The template functionality is to call the onSourceUpdate function provided, and simply set the value to the one returned by calculateSources. A control based on
this template might contain the following function. This example is taken from the d20 AC calculation, where the field contains a base number of 10, to which a series
of modifiers is applied. The onSourceUpdate function derived is called as a result of the self.onSourceUpdate call. It, in turn, calls the
calculateSources from the inherited environment, adds the number 10, and returns the result.
function onSourceUpdate()
setValue(10 + calculateSources());
end