Ruleset - Templates (for code re-use)

Overview

Templates allow window control definitions to be extended and enhanced. There are a number of reasons to use templates, including:

  1. Manage frequently used control definitions in one place.

  2. Use templates to allow ruleset layers and extensions to more easily override your interface for theming.

  3. Create a common look and feel for your interface (such as standard text entry fields), while allowing those elements to be customized within each interface. (Ex: For a standard text entry field, you may define a string field template with a common frame and font to be used. Then, you can include the text entry template within any window, and customize it further with layout information and logic specific to that window.)

  4. Compartmentalizing complex window control logic outside of the window class definition.

  5. Creating new control types, such as string cycling buttons or checkbox buttons.

Templates can be nested, meaning that a template is defined with another template as the source type. Taking merge behavior into account (see Merging sectino), 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.

From a technical perspective, templates allow single, multi-level inheritance for window controls, including script behavior.

When developing with templates, it is VERY useful to have multi-file search capability within your text editor to be able to find the templates used for control definitions and within other templates. This will allow you to drill into specific detail about how controls will work after applying templates.

Asset Definition

Template assets are defined using the "template" tag. The template tag must have a "name" attribute defined. The body of the template definition should start with a tag containing a type of windowcontrol being extended by the template. Alternatively, a template name can also be used to extend another template.

Example

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>

Example 2

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" position="insidetop" offset="-20,100" height="30" /> <frame name="bonus" offset="5,5,5,5" /> <font>sheetnumber</font> <displaysign /> <readonly /> <description text="Melee attack" /> <source> <name>attackbonus.base</name> <op>+</op> </source> <source> <name>abilities.strength.bonus</name> <op>+</op> </source> </linkednumber>

Usage

To use a template, declare a control as normal, treating the template name as the name of the control you want to create in the window class asset definition.

The template inherits any child tags, attributes and script blocks from the template. If templates are nested, then the inheritance is applied in a bottom up manner, where the template that does not refer to another template is resolved first. Generally, any tags defined in a template control definition or inherited from another template will override the tags in the template they inherit from. The process by which control definitions merge template tags are defined in the Merge Behavior section below.

Example

The following example is a window control definition within a window class that creates a control based on the "checkbox" template defined above. It supplements the original template by defining a location and tooltip for the control, and overrides the default icons used.

<checkbox name="spontaneous"> <anchored width="20" height="20"> <left offset="5" /> <bottom offset="-1" /> </anchored> <stateicons> <on>indicator_casterspontaneous</on> <off>indicator_casterprep</off> </stateicons> <tooltip text="Toggle spontaneous/prepared" /> </checkbox>

Merge Behavior

By default, the template asset 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 and attributes.

If both the control definition and the template have a similar tag with different child tags, then the default merging behavior will be applied.

  • A child tag defined with the same name will be merged with the first matching tag in the inherited template (both attributes and child tags).

  • If multiple child tags defined with the same name by the inheriting control or template, then they will each be individually merged. (i.e. not appended, but all merged)

Special attribute values (merge/mergerule) can be added to XML tags when using templates to define how tags can be merged in other ways. See the Advanced Merging section.

Example

For example, given this template asset definition:

And this window control definition within a window class:

The result of the merge will be:

Advanced Merging

Simply merging is not sufficient for some uses in templates. Therefore, any inheriting template or control definition can use the "merge" attribute, or any inherited template definition can use the "mergerule" attribute, can define an alternate merging behavior. If multiple child tags exist in the definition with the same name (i.e. a list of similar child tags), only the topmost is checked when looking for a merge or mergerule attributes. If both are defined in the inheriting and inherited definitions on the same child tag, then the "merge" attribute gets priority.

The merge and mergerule attributes can have the following values:

  • merge: Synonymous with the default merge behavior noted above.

  • replace: If the inheriting control or template definition contains a tag with the same name 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.

  • delete: This child tag and all child tags in the inherited template definition are removed from the merged XML result.

Example 2

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.

Scripting

Overview

Each template has its own script environment, similar to a normal script block in a control. Any control definition or template definition derived from a template has a separate environment for its script block, as well.

Scope

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.

Bypassing Scope

To remedy the problems described above, there are two special variables defined in each script block inheriting another.

The "self" variable 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 "super" variable refers to the inherited template script block environment. (i.e. one level down) 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.

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.