Next, we'll find out how to add custom properties to XBL-defined elements.
The XBL Interface
There are three types of items you can add. Fields are used to hold a simple value. Properties can also be used to hold a value but may have code execute when an attempt is made to retrieve or modify the value. Methods are functions which may be executed.
All three are defined within an implementation element, which should be a child of the binding element. Within the implementation, you define individual field, property, and method elements, one for each one that you want. The general syntax is as follows:
<binding id="element-name"> <content> -- content goes here -- </content> <implementation> <field name="field-name-1"/> <field name="field-name-2"/> <field name="field-name-3"/> <property name="property-name-1"/> <property name="property-name-2"/> <property name="property-name-3"/> . . . <method name="method-name-1/> -- method content goes here -- </method> . . . </implementation> </binding>
Each field is defined using the field element. Often, fields would correspond to an attribute placed on the element such as label or disabled, but they do not have to.
The name attribute on the field element is used to indicate the name of the field. You can use the name from a script to get and set the value. The example below creates a button which generates and stores a random number. You can retrieve this same number multiple times by getting the number property from the button. Most of the work here is done in the oncommand handlers. Later, we'll find out how to move this to XBL.
XUL: <box id="random-box" class="randomizer"/> <button label="Generate" oncommand="document.getElementById('random-box').number=Math.random();"/> <button label="Show" oncommand="alert(document.getElementById('random-box').number)"/> XBL: <binding id="randomizer"> <implementation> <field name="number"/> </implementation> </binding>
A number field has been defined in the binding, which stores the random number. The two extra buttons set and get the value of this field. The syntax is very similar to getting and setting the properties of HTML elements. In this example, no content has been placed inside either the XUL box or its definition in XBL, which is perfectly valid.
This example isn't quite correct because the field is not assigned a default value. To do this, add the default value as the content of the field tag. For example:
<field name="number"> 25 </field>
This will assign the value 25 as the default value of the number field. Actually, you can instead place a script inside the field tag that evaluates to the default value. That might be necessary if the value needs to be computed. For example, the following field is given a default value equal to the current time:
<field name="currentTime"> new Date().getTime(); </field>
Sometimes you will want to validate the data that is assigned to a property. Or, you may want the value to be calculated dynamically as it's asked for. For example, if you want a property that holds the current time, you would want to have its value generated as needed. In these cases, you need to use a property tag instead of a field tag. Its syntax is similar but has additional features.
You can use the onget and onset attributes to have code execute when the property is retrieved or modified. Add each to the property element and set its value to a script which either gets or sets the value of the property.
For example, you could assign a script to the value of onget to calculate the current time. Whenever a script attempts to access the value of the property, the onget script will be called to retrieve the value. The script should return the value that should be treated as the value of that property.
The onset handler is similar but is called whenever a script attempts to assign a new value to the property. This script should store the value somewhere, or validate the value. For example, some properties might only be able to store numbers. Attempting to assign alphabetic text to such a property should fail.
<property name="size" onget="return 77;" onset="alert('Changed to:'+val); return val;"/>
This property will always return 77 when retrieved. When set, an alert will be displayed which displays the value to assign to the property. The special variable val holds the value that the property should be assigned to. Use this to validate it or store it. The onset code should also return the new value.
The following decribes what happens in a typical case:
There are two elements, one called 'banana' and the other 'orange'. They each have a custom property called 'size'. When the following line of script is executed:
banana.size = orange.size;
- The onget script is called for the size property of the orange. The script calculates the value and returns it.
- The onset handler of the size property of the banana is called. This script uses the value passed in the val variable and assigns it to the size property of the banana in some manner.
Note that unlike a field, a property does not hold a value. Attempting to set a property that does not have an onset handler will generate an error. You will often use a separate field to hold the actual value of the property. It is also common to have the properties match an attribute on the XBL-defined element. The following example maps a property to an attribute on an element.
<property name="size" onget="return this.getAttribute('size');" onset="return this.setAttribute('size',val);" />
Whenever a script attempts to get the value of the property, it is grabbed instead from the attribute on the element with the same name. Whenever a script attempts to set the value of a property, it is set as an attribute on the element. This is convenient because then you can modify the property or the attribute and both will have the same value.
You can use an alternate syntax for the onget and onset attributes that is useful if the scripts are longer. You can replace the onget attribute with a child element called getter. Similarly, you can replace the onset attribute with a setter element. The example below shows this:
<property name="number"> <getter> return this.getAttribute('number'); </getter> <setter> var v = parseInt(val,10); if (!isNaN(v)) return this.setAttribute('number',''+v); else return this.getAttribute('number');" </setter> </property>
The property in this example will only be able to hold integer values. If other characters are entered, they are stripped off. If there are no digits, the value is not changed. This is done in the code inside the setter element. The real value of the property is stored in the number attribute.
You can use either syntax for creating get and set handlers.
(Next) The next section shows how to add methods to XBL-defined elements.
(Is there a better way to do this?) (Yes, use a <constructor>).