Animation

The code for the animation of the marquee is fairly simple. Inside the element we put an anonymous container (more about this later) that is relatively positioned. We use a timer (window.setTimeout) and every time this is triggered the position of the anonymous container is updated and the timer is started again.

XBL Structure

First we create the XML markup that is needed to define the binding. The root element in an XBL file must be called bindings. The bindings element can contain multiple binding elements and therefore more than one binding can be defined in each XBL file.

<?xml version="1.0"?>
<bindings xmlns="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml">

<binding id="marquee" applyauthorstyles="true">
<content>
   ...
</content>
<implementation>
   ...
<implementation>
</binding>

</bindings>

For a XUL and XBL Tutorial take a look at XULPlanet.

Content

This is basically the easiest part of the XBL binding. Here you can add any XML that you want. For the marquee we add a html:span and inside this span we insert all the child elements from the marquee. To insert the children we use the special XBL element children.

<content>
   <html:span style="/* lots of CSS */">
      <children/>
   </html:span>
</content>

The CSS makes the span relatively positioned and floating. It also overloads all CSS properties that might be inherited from the document where the marquee will reside. Take a look at the source if you want to see the complete list.

Anonymous Content

The html:span inserted above is an anonymous node. This means that it will not be visible in the DOM tree and therefore we need a special method to find that node when we script the animation.

var innerContainer = document.getAnonymousNodes(element)[0];

Implementation

Inside the implementation element we define the methods and properties that we want the marquee to have.

<implementation>
   <constructor>
      ...
   </constructor>
   <method />
   <property />
</implementation>

Once again I point you the XUL and XBL Tutorial at XULPlanet for a complete reference.

Constructor

XBL has some serious scoping issues. One of these is that there is no top level variable or function scope. This means that we cannot create any private variables nor methods that can be shared among the public methods and properties. Therefore we create one public JS expando object that contains all the needed info. This property will be called __marquee__ and will be created in the construct block.

<constructor>
//<![CDATA[

var element = this;			

this.__marquee__ = new (function () {
   ...
});

//]]>
</construct>

This might look a little bit weird but it allows us to bind all the methods to the __marquee__ object and then we can just map the XBL methods and properties to the __marquee__ properites.

Methods

Now that we have defined the JS object __marquee__ we just need to map the methods to that:

<method name="stop">
   <body>
      this.__marquee__.stop();
   </body>
</method>
<method name="start">
   <body>
      this.__marquee__.start();
   </body>
</method>

Properties

The declaration of the properties are also very simple now that we have the __marquee__ object. All we need to do is to define the setters and getters using the JS object.

<property name="loop"
   onset="this.__marquee__.setLoop(val); return this.__marquee__.getLoop();"
   onget="return this.__marquee__.getLoop();"/>

Notice how the value passed to the setter is called val.

Attributes and properties

One important thing to remember in XBL is that XBL distinguishes between attributes and properties. Therefore nothing will happen if one uses setAttribute("loop", 5). The same problem occurs at startup if the markup contains the attribute loop="5". Therefore we need to add some extra code to handle these cases.

First we look at how the attribute is read at startup. This is done inside the constructor and we just read the attribute and if not set we use a default value.

// returns the value of the dom attribute
function getPropAttr(sProp, vDefault) {
   var domAttr = element.getAttribute(sProp)
   if (domAttr != null && domAttr != "") return domAttr;
   return vDefault;
}

// define the properties and find the initial value
var _scrollDelay  = Number( getPropAttr("scrollDelay", 85) );
var _scrollAmount = Number( getPropAttr("scrollAmount", 6) );
var _loop         = Number( getPropAttr("loop", -1) );
var _direction    = getPropAttr("direction", "left");
var _behavior     = getPropAttr("behavior", "scroll");

That was fairly straight forward but we also need to catch the attribute changes after the marquee has been initialized. To do this we need to overload the default setAttribute (and getAttribute). What we do is that we check the attribute name and if it is one of the attributes that we have defined for the marquee we call the equivalent method for the __marquee__ object.

element.__marquee__setAttribute = element.setAttribute;  // backup original
element.setAttribute = function (sAttrName, sAttrValue) {
   switch (sAttrName.toLowerCase()) {
      case "loop":
         this.__marquee__.setLoop(sAttrValue);
         break;
      case "scrollamount":
         this.__marquee__.setScrollAmount(sAttrValue);
         break;
      case "scrolldelay":
         this.__marquee__.setScrollDelay(sAttrValue);
         break;
      case "behavior":
         this.__marquee__.setBehavior(sAttrValue);
         break;
      case "direction":
         this.__marquee__.setDirection(sAttrValue);
         break;
      case "onbounce":
         this.__marquee__.setInlineEventListener("bounce", sAttrValue);
         break;
      case "onstart":
         this.__marquee__.setInlineEventListener("start", sAttrValue);
         break;
   }
   return this.__marquee__setAttribute(sAttrName, sAttrValue);
};


element.__marquee__getAttribute = element.getAttribute;  // backup original
element.getAttribute = function (sAttrName) {
   switch (sAttrName.toLowerCase()) {
      case "loop":
         return this.__marquee__.getLoop();
      case "scrollamount":
         return this.__marquee__.getScrollAmount();
      case "scrolldelay":
         return this.__marquee__.getScrollDelay();
      case "behavior":
         return this.__marquee__.setBehavior();
      case "direction":
         return this.__marquee__.setDirection();
      case "onbounce":
         return this.__marquee__.getInlineEventListener("bounce");
      case "onstart":
         return this.__marquee__.getInlineEventListener("start");
   }
   return this.__marquee__getAttribute(sAttrName);
};

Events

The marquee element fires two special events. One when started and one when a bounce occurs. Firing an event is easy in Mozilla and can be done with the following code:

function fireEvent(sType) {
   var e = document.createEvent("Events");
   e.initEvent(sType, false, false);
   element.dispatchEvent(e);
}

The only problem with this is that only event listeners added using addEventListener are notified. As you saw in the setAttribute and the getAttribute code above we also add the possibility to use setAttribute to add an event listener. We have also added code to listen for the properties onbounce and onstart to the implementation section.

<property name="onbounce"
   onset="this.__marquee__.setInlineEventListener('bounce', val); return this.__marquee__.getInlineEventListener('bounce');"
   onget="return this.__marquee__.getInlineEventListener('bounce');"/>
<property name="onstart"
   onset="this.__marquee__.setInlineEventListener('start', val); return this.__marquee__.getInlineEventListener('start');"
   onget="return this.__marquee__.getInlineEventListener('start');"/>

What these do behind the scenes is that they add the function as an event listener using addEventListener. It also binds the function to the __marquee__ object so that it can be removed or returned later on.

this.setInlineEventListener	= function (sType, v) {
   if (inlineEventListerners[sType] != undefined) {
      element.removeEventListener(sType, inlineEventListerners[sType], false);
      delete inlineEventListerners[sType];
   }
		
   var f;
   if (typeof v == "function")
      f = v;
   else if (typeof v == "string")
      f = new Function("event", v);

   if (f != undefined) {
      inlineEventListerners[sType] = f;
      element.addEventListener(sType, f, false);
   }
};
this.getInlineEventListener	= function (sType) {
   return inlineEventListerners[sType];
};

Conclusions

That was a lot of code and still we did not even touch the actual implementation of the animation. The problem with XBL is that it does not handle all the necessary stuff and let the developer concentrate on the actual implementation of the component. But, once you have created one binding that handles all these boring stuff you can reuse it for your other bindings so that you can concentrate on the real problem instead of the boring underlying stuff.

XBL Marquee
Implementation
Demo
Progressbar Demo
Download

Author: Erik Arvidsson