This section will describe an example XBL element.
Let's construct a full example of an XBL element. This will be a widget that stores a deck of objects, each displayed one at a time. Navigation buttons along the bottom will allow the user to cycle through the objects. A text widget between the buttons will display the current page. You could put anything with-in the pages, however, this might be useful for a set of images. We'll call this a slideshow element.
First, let's determine what elements need to go in the XBL content. Since we want page flipping, a deck element would be the most suitable to hold the page content. The content of the pages will be specified in the XUL file, not in XBL, but we'll need to add it inside the deck. The children tag will need to be used. Along the bottom, we'll need a button to go the previous page, a text widget to display the current page number, and a button to go to the next page.
Example 10.9.1
<binding id="slideshow">
<content>
<xul:box orient="vertical" flex="1">
<xul:deck index="0" flex="1">
<children/>
</xul:deck>
<xul:box orient="horizontal">
<xul:button xbl:inherits="value=previoustext"/>
<xul:text flex="1"/>
<xul:button xbl:inherits="value=nexttext"/>
</xul:box>
</xul:box>
</content>
</binding>
|
This binding creates the slideshow structure that we want. The flex attribute has been added to a number of elements so that it stretches in the right way. The value attributes on the two buttons inherit their values from the bound element. Here, they inherit from two custom attributes, previoustext and nexttext. This makes it easy to change the labels on the buttons. The children of the element that the XBL is bound to will be placed inside the deck.
The following XUL file produces the result in the image.
<box class="slideshow" previoustext="Previous" nexttext="Next" flex="1"> <button value="Button 1"/> <button value="Button 2"/> <button value="Button 3"/> </box> |
The elements have been placed as defined. The first button, 'Button 1' has
been used as the first page of the deck. The text
widget has not appeared as no value has been
specified for it. We could set a value, but instead it will calculated later.
Next, a property that holds the current page will be added. When getting this custom property, it will need to retreive the value of the index attribute of the deck, which holds the number of the currently displayed page. Similarly, when setting this property, it will need to change the index attribute of the deck. In addition, the text widget will need to be updated to display which page is the current one.
<property name="page"
onget="return parseInt(document.getAnonymousNodes(this)[0].childNodes[0].getAttribute('index'));"
onset="return setPage(val);"/>
|
The 'page' property gets its value by looking at the first element of the anonymous array. This returns the vertical box, so to get the deck, we need to get the first child node of the box. The anonymous array isn't used then as the deck is not anonymous from the box. Finally, it gets the value of the index attribute and returns it. To set the page, a method 'setPage' is called which will be defined later. The default value of the property is not specified as it just gets its value from the deck.
An onclick handler will need to be added to the Previous and Next buttons so that the page is changed when the buttons are pressed. Conveniently, we can change the page using the custom 'page' property that was just added:
<xul:button xbl:inherits="value=previoustext"
onclick="parentNode.parentNode.parentNode.page--;"/>
<xul:text flex="1"/>
<xul:button xbl:inherits="value=nexttext"
onclick="parentNode.parentNode.parentNode.page++;"/>
|
Since the 'page' property is only on the outer XUL element, we need to to use the parentNode property to get to it. The first parentNode returns the parent of the button which is the horizontal box, the second its parent, the vertical box, and finally, its parent which is the outer box. The 'page' property is incremented or decremented. This will call the onget script to get the value, increment or decrement the value by one, and then call the onset handler to set the value.
Now let's define the 'setPage' method. It will take one parameter, the page number to the set the page to. It will need to make sure the page is not out of range and then modify the deck's index attribute and the text widget's value attribute.
<method name="setPage">
<parameter name="newidx"/>
<body>
<![CDATA[
var thedeck=document.getAnonymousNodes(this)[0].childNodes[0];
var totalpages=childNodes.length;
if (newidx<=0) return 0;
if (newidx>totalpages) return totalpages;
thedeck.setAttribute("index",newidx);
document.getAnonymousNodes(this)[0].childNodes[1].childNodes[1]
.setAttribute("value",newidx+" of "+totalpages);
return newidx;
]]>
</body>
</method>
|
This function is called 'setPage' and takes one parameter 'newidx'. The body of the method has been enclosed inside '<![CDATA[' and ']]>'. This is the general mechanism in all XML files that can be used to escape all of the text inside it. That way, you don't have to escape every less-than and greater-than sign inside it.
Let's break down the code piece by piece.
var thedeck=document.getAnonymousNodes(this)[0].childNodes[0];Get the first element of the anonymous content array, which will be the vertical box, then get its first child, which will be the deck element.
var totalpages=childNodes.length;Get the number of children that the bound box has. This will give the total number of pages that there are.
if (newidx<=0) return 0;If the new index is before the first page, don't change the page and return 0. The page should not change to one earlier than the first page.
if (newidx>totalpages) return totalpages;If the new index is after the last page, don't change the page and return the last page's index. The page should not change to one after the last page.
thedeck.setAttribute("index",newidx);
Change the index attribute on the deck. This
causes the requested page to be displayed.
document.getAnonymousNodes(this)[0].childNodes[1].childNodes[1].setAttribute("value",newidx+" of "+totalpages);
This line modifies the text widget so it displays the current page index.
The text widget can be retrieved by getting the first element of anonymous
content (the vertical box), the second child of it (the horizontal box) and
then the second element of that box. The value
attribute is changed to read '1 of 3' or something similar.
The only remaining issue is that the text widget is not set by default. The final result is shown in the following image:
We can add some additional features as well. Some keyboard shortcuts could be used for the Previous and Next buttons, (say backspace and the space bar). First and Last buttons could be added to go to the first and last pages. The text widget could be changed to a field where the user could enter the page to go to, or a popup could be added to allow selection of the page from a menu. We could also add a border around the deck with CSS to make it look a bit nicer.
The final code is as follows:
Example 10.9.2
<binding id="slideshow">
<content>
<xul:box orient="vertical" flex="1">
<xul:deck index="1" flex="1">
<children/>
</xul:deck>
<xul:box orient="horizontal">
<xul:button xbl:inherits="value=previoustext"
onclick="parentNode.parentNode.parentNode.page--;"/>
<xul:text flex="1"/>
<xul:button xbl:inherits="value=nexttext"
onclick="parentNode.parentNode.parentNode.page++;"/>
</xul:box>
</xul:box>
</content>
<implementation>
<property name="page"
onget="return parseInt(document.getAnonymousNodes(this)[0].childNodes[0].getAttribute('index'));"
onset="return setPage(val);"/>
<method name="setPage">
<parameter name="newidx"/>
<body>
<![CDATA[
var thedeck=document.getAnonymousNodes(this)[0].childNodes[0];
var totalpages=childNodes.length;
if (newidx<=0) return 0;
if (newidx>totalpages) return totalpages;
thedeck.setAttribute("index",newidx);
document.getAnonymousNodes(this)[0].childNodes[1].childNodes[1]
.setAttribute("value",newidx+" of "+totalpages);
return newidx;
]]>
</body>
</method>
</implementation>
</binding>
|
(Next) The next section discusses how to make a XUL application localizable.