XBLinJS Documentation

(If your browser supports CSS for printing, this should print out relatively nicely, so this is also the "for printing" version of the documentation. I recommend against deep linking into this document, the headings may change.)

Status: Beta quality on Mozilla and IE 6, unknown on IE 5, completely untested in all other alternate browsers.

Advanced node: This is intended as a minimal reference for using XBLinJS across HTML browsers, which I believe will be the most common use. For using XBLinJS as a straight XBL replacement, see the Advanced Uses document.

Introduction

Several good introductions to XBL have been created (and linked on the XBLinJS homepage), so I will not go into too much detail about what XBL is. In summary, XBL is a way of creating rich HTML widgets in a principled way, instead of as ugly Javascript function scattered willy-nilly and constantly re-inventing the wheel.

This documentation will document the correspondences between XBLinJS and XBL, but will endeavor to explain what XBLinJS does well enough that someone who has never heard of XBL can understand and use the library; since it is impossible to read documentation with "fresh eyes", feedback would be appreciated; I pride myself on my documentation.

All relevant public methods are documented (or it's a bug); use the find function in your browser to find the one you are looking for.

It is assumed that you:

Requirements

XBLinJS only has a chance of working in a sufficiently strong Javascript interpreter. The interpreter:

And the browser must support at least DOM 1, which should be everything current.

XBL Feature Comparison

This section will match up what capabilities of XBL are implemented effectively perfectly or better, which are kludged, and which are unimplemented, with a note as to whether that is permanent or temporary. If you don't know anything about XBL you may wish to skip this section.

If there are capabilities not mentioned here please email me and tell me. I keep going over this list in my head but it is so long it is easy to forget pieces of it.

Fully Implemented Or Better:

Kludged

Not Implemented

Added

Installing XBLinJS

When you download the XBLinJS package, the XBLinJS files will be contained in a "src" directory. For a full install, move those files into some location you can reach with your webserver.

For a minimal install, copy only "jobject.js", "flavors.js", and "widgets.js" into an appropriate location.

HTML files that wish to use XBLinJS will need to minimally include those files, in that order. (You may concatentate them together into one file for your convenience, but I recommend only doing that on final deployment.)

Optionally, you may copy "debug.html" out of the "extras" directory into the directory with the other XBLinJS files. "debug.html" is a simple HTML file that exposes a Javascript Console widget for your use.

Uninstalling is, of course, simply removing the files you added. Since XBLinJS is a pure Javascript library it can't make any other new files or do anything to any system configuration.

What Is A Widget?

A "widget" consists of the following things, which will be examined in detail in the following sections:

Finally, we need some way to insert the widgets into your document, which is an ugly problem, but is solvable in several ways.

Deriving From "Widget"

To create a new Widget, you need to derive from the Widget class. Creating a new Widget class is fairly complicated, so a function is provided to help you do it:

deriveNewWidget

deriveNewWidget(newName, baseWidget, extraInstanceInit, extraClassInit): Creates a new Widget class.

newName is the class name of the desired new Widget type, as a string.

baseWidget is a reference to the desired base Widget type. (It will take a string and look it up via WidgetNameToType, but I recommend using the reference.)

extraInstanceInit is a function that will be run after a new widget has been fully initialized. It will be passed the widget as the first and only parameter. This is for advanced usage and should generally not be used. (In rare cases, for instance, it can be useful to run certain advanced initialization only on "leaf" types, and this is the easiest way to do it I've yet found.)

extraClassInit is a function that will be run once on the widget class after it is defined and created. It is a function that will be run once on an uninitialized instance of the Widget when it is created. This is accomplished by setting new_widget.prototype.initClass to the function, creating a new instance of the class without initializing it, and calling .initClass() on it. That means that it will be inherited by subsequent classes. Note that when it is called, no other declarations will have been processed yet, so you can not access any overridden parameters. It is highly unlikely that you will need this; XBLinJS uses this to implement its WidgetNameToType object (the initialization func for the Widget class catalogs the newly created Widget in that object; any subsequent overriding of the init parameter should pass the call up to Widget.prototype.initClass in addition to whatever else they do).

Getting A Widget Instance and Adding It To The Document

Getting an instance of a widget class is like getting an instance of any other Javascript class, except of course you have to pass the parameters as an object:

var label = new LabelWidget({value: "Hello!"})

will create a Label widget that will have the initial value of "Hello!".

You can not add a widget directly to the document, of course, because it is not a DOM node. As discussed in the "Content" section, all Widgets have one DOM node that represents them; this will be available as the .domNode attribute on the widget, or you can use the provided DOM(widget) function to retrieve it. (The DOM function retrieves the .domNode of a Widget, or returns the passed value unchanged; this allows you to "flatten" the difference between a DOM node and a Widget by running it through the DOM() function.) Thus, to add the Label widget to some "panel" node, use the following:

panel.appendChild(DOM(label))
Default Attributes

Classes may declare .defaults for their attributes. The .defaults are specified as a Javascript object like the atts object in a constructor. Before any .init* methods are called, any parameters that exist in the .defaults object, but not in the atts object, will be copied over by reference.

Widget Object Details

The Widget library will add the following attributes to each widget object instance:

Widget Content

A Widget, in order to be "real", must of course consist of some set of concrete DOM nodes in the HTML document. This is one of the clumsiest parts of the translation of XBL into Javascript, yet also one of the greatest sources of strength, since you can use the full power of Javascript expressions, which XML is notably lacking.

The "content" that defines the widget is defined as the .content method of the widget class, and is a Javascript object that defines a tag to serve as the top-level DOM node of the widget. A simple example of a widget like the label, being held together by a span element:

LabelWidget.prototype.content = {tagName: "span", name: "textTarget"};

Every time a LabelWidget is instantiated, the Widget definition of .initDOM will create a span DOM node. That DOM node will be stored as the .textTarget attribute of the resulting Widget, so for instance you could affect the class of the span node on a LabelWidget named "label" with the following:

label.textTarget.className = "newClass"

There are several special attributes that can be used here to affect how XBLinJS will create the DOM node and what it does with the node. Any other attributes will be collected and passed to the DOM node itself, either via a series of .setAttribute() calls on the resulting DOM node, or in the parameter object to a new sub-Widget. The special parameters are:

Working backwards from what you are probably more familiar with, the HTML fragment

<body bgcolor="#FFFFFF"><p>hello</p></body>

converts into

{tagName: "body", bgcolor: "#FFFFFF",
  children: [
    {tagName: "p", children: ["hello"]}
]}

Note how "bgcolor" is not one of the special attributes and is thus passed through to the body tag.

If the .content member is set to a false value, no processing will be done at all. Your new .initDOM is expected to create some DOM node and assign it to .domNode in the new widget instance. Failure to do this will break pieces of the Widget code. If you create a widget that doesn't need a DOM node, then odds are it isn't really a Widget at all. (Although I have a few that are still widgets as they want to tap into the Widget event handling; see the HistoryAddin.)

More examples may be found in the various provided widgets. Also note that you have Javascript available to you, so you may use functions or variables to collect common expressions or provide shortcuts for commonly used fragments.

The .content member will be checked for certain constraints to validate proper cross-browser functionality, to be collected and expanded as the project learns the hard way about what those are and whether we can check for them. As of the .5 release, for instance, the .content is checked for two things:

  1. All table tags have a tbody tag; without this, Internet Explorer won't display the table, even though if you product HTML without a tbody tag it is fine.
  2. You don't use "class" to try to specify a CSS class for some node; cross-browser differences in how "class" and "className" are handled are particularly peculiar. XBLinJS tries to handle them for you, but you need to use "className" to set CSS classes.

If you define an object that has neither tagName nor widgetType, it will be passed to .createObjectCustom(objDef, child), where objDef is the object that has neither attribute, and child is a flag indicating whether this object is a child object. (This is used in the standard createObject just to add the .widget reference to the root widget, if child is false; it is otherwise unused.) Your .createCustomObject should return either a Widget or a DOM node, a false value to skip the definition, or throw an error if there is a problem with the custom object. (The default implementation automatically throws an error on all input.)

This can be used for various advanced construction techniques on a per-class basis; for instance, I have a class where I have "headers", "rows", and a "footer", but sometimes I use a table and sometimes I just use divs. I create a superclass that can do things like sort the "rows", and the superclass defines a Javascript object in its .content like this: {reference: "header"}. In its createObjectCustom, it knows that means to grab this object's .header attribute and recursively feed it back into .createObject (which most .createCustomObjects should probably do). This allows me to apply the Template Pattern to my .content. Don't try this in XBL. The code is as follows:

ChildEditor.prototype.createObjectCustom = function (objDef, child){
  if (objDef.reference) {
    return this.createObject(this[objDef.reference]), child);
  }
  return false;
}
.initData(atts), .initDOM(atts) and .initWidget(atts) (Widget Initialization)

Widget initialization is broken up into three parts to avoid a problem I kept running in to with XBL in Mozilla. I wanted to create XBL widgets that dynamically created other XBL widgets as children, based on metadata received from the outside. However, the children XBL widgets weren't "real" until they were displayed on the screen, meaning I could not call methods on them; they were just standard, dumb DOM nodes. (And of course there is no event you can catch representing "rendering".)

Ultimately, while I understand the technical reasons behind this issue, it still caused me no end of trouble. One workaround is to pass parameters in by setting attributes on the new XBL nodes, but there was no workaround for getting data back out, of course, and the relevant hacks were breaking my encapsulation and making the flow control even more difficult to understand than OO techniques already make it.

Breaking the setup into three phases allows all relevant DOM manipulation to complete before we try to do anything to initialize the widget's data, guaranteeing we aren't initializing something that doesn't exist yet. This allows a lot of dynamic widget creation to take place without DOM initializations crossing data initializations. If you've never run into this problem, then please just take it on faith that maintaining this separation as purely as possible in your derived widgets will save you from some lengthy debugging sessions if you get fancy with XBLinJS.

.initData(atts) is an opportunity to initialize data before .initDOM is called. The default implementation manages setting up various Widget services like the inheritance manager and Variable storage, and should be called if you override this method.

.initDOM(atts) need not be overridden at all if the mechanisms described in the Content section are adequate for your widget, which should be a common case. Otherwise, .initDOM overrides should limit themselves to affecting that process, and creating new DOM nodes or subwidgets as needed. Most widgets shouldn't even need the atts parameter, though there are times it is useful.

Remember that in Javascript we can call the superclass constructors whenever we choose, as they are not automatically called, so .initDOM can, if it chooses, manipulate .content before the superclass creates the nodes based off of it. To prevent issues that may arise from trying to modify the prototype of the object itself, a function objCopy is provided which will return a deep copy of a simple Javascript object (no prototype, no fancy class other than Object). It would be prudent to use this before manipulating the .content:

this.content = objCopy(this.content)

That should prevent corruption of the prototype.

.initWidget(atts) should set up any other necessary initialization. It should generally consume all the relevant parameters from atts. It can also perform relevant initialization of child nodes and widgets; in most browser implementations I've seen, these manipulations will take effect before the widgets or nodes are displayed.

Default parameters are implemented in the Widget initialization by calling addDefaultsToAttObject, before any code you can override. Attributes defined in the class's .prototype.defaults are copied into the atts object if they don't already exist, thus the .init* methods do not need to do any special "if (this.something == undefined)" type checking. Note that .initDOM will call it again, so deleted attributes with a default won't stay deleted in that (rare) case.

The default .initWidget will take any remaining attributes and call .setAttribute(key, value) on the Widget. If you do not want this to occur, consume the attribute by deleting it before you call the Widget .initWidget method. This is an extremely useful default due to the power of .setAttribute; many widgets can be completely initialized with a combination of .defaultss and .set_* methods, which makes it easy to write just one .set_* method which is used for both initialization and later re-settings.

By default, the order chosen to call the .setAttribute on the keys is the iteration order Javascript chooses for the object's keys, which should not be depended on. You can set the attribute .initOrder on a Widget to an Array of strings, indicating the order to call .setAttribute with the key on the Widget. Remaining attributes will still be set in the Javascript order.

.appendChild

.appendChild(element, target)

Adds the DOM node or widget element to the target append point. "default" is used as the target if none is given.

Methods

There is nothing special about the methods of a Widget, just as there is nothing particularly special about the methods of an XBL object. Remember to manually call superclass methods as appropriate.

Event Handling

XBL uses the same basic event system as conventional HTML, but modifies it to work with its inheritance system. If you derive an XBL widget A->B->C and each declares a keypress handler, when a keypress is received in C, each handler in the inheritance chain will be called: First A's, then B's, then C's.

XBLinJS extends event handling even further. When you use registerEvent to catch events, XBLinJS catches and passes the event to each registered handler, in inverse order of registration. (Note that the DOM2 Event Model explicitly does not guarantee order; XBLinJS does.) If any handler returns false, XBLinJS stops passing the event and returns "false" to the browser, which typically cancels the event.

This "chaining" of event handlers was created to support adding Dropdown widgets as transparently as possible to any other widget; a Dropdown widget will register handlers necessary to implement scrolling and selection in the dropdown, but will transparently pass all other events along to the underlying widget. That is to say, widgets can be masked with other widgets which will receive the events they care about, but transparently pass all others on. Another current use is the HistoryAddin, which provides history services to the Javascript Console. Another possible use might be a Help widget, that in addition to displaying a graphic, might also capture a keypress as a shortcut. XBL can't do this unless you program it in yourself, against the grain of its own event handling ideas.

If you want to pass an event up to a superclass, you must do that manually. This also allows a child class to fully override a parent's event handling, which is not directly possible in XBL, though if you control the super-XBL-widget you can work around it by adding a hook to the top event handler.

Widget.registerEventHandler

Widget.registerEventHandler(eventType, handleMethod, target, context):

registerEventHandler registers the event type eventType (i.e., "blur", "focus", "keypress") to be processed with the method named in handleMethod, which will receive the event as its only parameter. target is the XBLinJS name of a subwidget to register the event for; a false or missing value will register it on the top-level DOM node for the widget.

context is either a reference to a widget, or the .widgetId of a widget, representing in what context you wish to run the event handler in. This is an advanced parameter and is generally only needed for Widgets like the Dropdown widget that wish to "attach" to other widgets and receive events from DOM nodes they don't own. For instance, the Dropdown widget registers a keypress handler with the target widget, but passes its own .widgetId into the registerEventHandler call, so the Dropdown widget object receives the event.

Examples, for a widget named "widget":

widget.registerEventHandler('blur', 'blurHandler'):

When the main widget receives a 'blur' event, XBLinJS will call the 'blurHandler' method of the widget with the event. 'blurHandler' should be defined like WidgetClass.prototype.blurHandler = function (event) {}.
Automatic Event Handler Registration

Widget methods named on[event] will automatically be registered as event handlers on that widget during DOM initialization.

Widget methods named on[event]_[name] will be registered as an event handler, but only on the DOM node named by name. For instance, if you have a DOM node or widget named "button", a method named onclick_button will be registered as the onclick handler as for the "button" node.

Note that all such event handlers will run in the context of the Widget the method is a member of, not the sub-widget. I.e., in the example above, if "button" is itself a widget, the root widget will still receive the event. (This should be how you expect it to work intuitively, but it should be mentioned.)

Properties, "Variables", and Data Handling

XBL allows the creation of "properties", methods that are called when an attribute on an object is set or retrieved. For instance, if a "getter" is created for the "length" attribute, whenever object.length is used somewhere, Javascript translates that to a function call to the "getter" and returns the value of the function call.

This is quite powerful, but at the moment and to my knowledge, only Mozilla-based browsers (of the major browsers) support properties in Javascript. To emulate the good qualities of this, XBLinJS implements the standard .getAttribute and .setAttribute methods from the DOM, but does a cascading lookup instead of a pure attribute retrieval.

XBLinJS provides two ways of emulating properties. One emphasizes making it easy to write one-off properties, and one emphasizes re-use of common property types. In addition to that, inherited attributes are reflected through this interface as well. Thus, there are four things that .setAttribute or .getAttribute may do.

The first type of property emulation, and the first thing that *Attribute looks for, is specially named functions; for an attribute named "att1", .setAttribute will look for a function named .set_att1 and .getAttribute will look for a function named .get_att1. "Setter" function receive one argument, the desired set value, and "getter" functions receive no arguments. This makes it easy to write one-off attributes.

The second type of property emulation, and the second thing *Attribute looks for, is a Variable. A Variable maps something in the DOM representation of the widget to this value system, which allows you to match how DOM nodes normally behave, where, for instance, calling .setAttribute("value", "hello") on a text node will not only cause a variable to change, but the text in the text box to change as well. A Variable takes a reference to the widget, and the name of the node it should bind to, and has two methods, .get taking no arguments and .set taking the new value.

XBLinJS provides a "value variable" that does just that; it binds to something that has a "value" like a text box, and sets and retrieves the value of the text box, which makes it look like the text box value is just part of the widget itself. This allows for good encapsulation.

Variables must be registered by calling .declareVariable, see documentation below, or may be declared by the attribute .Variables on the prototype, which is an array of four-element arrays, where the entries in the array correspond to the parameters in the .declareVariable call.

Third, if there is an inherited attribute by that name, .*Attribute will set or retrieve that inherited attribute. In the retrieval case, if there are multiple things that are inheriting that attribute, only the first will be retrieved. This should work correctly as long as you do not manually twiddle such attributes without using .*Attribute.

Finally, last and least, if neither a property nor a Variable exists with the name, .getAttribute and .setAttribute will set an attribute on the Javascript object representing the widget itself. For the sake of future-proofing, attribute access should generally go through *Attribute to make it easy to replace an attribute with a property or Variable later.

Do not depend on this order; the order is not tested in the unit testing, only the fact that all these things work, so the order is only weakly guaranteed for 0.5 and may change in later versions. Generally speaking, you should not need to know about this hierarchy, as you should not create naming conflicts. I am thinking of detecting these in later versions of this library and throwing an error instead, as it is probably something you'd only do accidentally.

declareVariable

declareVariable(varName, varType, value, extra)

varName is the name of the new variable, which will be accessed via .*Attribute.

varType is a reference to a Javascript class which implements the Variable. By default, this will be a ValueVar, which takes in its extra parameter the Widget name of the node to attach to.

value is the default value of the variable, which may be left off.

extra is any extra initialization the variable type may require. This may be left off.

For example, if you have a text input field named text in some widget, the call

widget.declareVariable("textValue", ValueVar)

will allow you to set and retrieve the value of the input field via a call to .setAttribute("textValue", ...) or .getAttribute("textValue").

Attributes Passed To Constructor

When a widget is created, attributes are passed to the constructor as documented earlier:

var label = new LabelWidget({value: "Hello!"})

While you can look at .atts directly in your .init* functions, any remaining keys and values will result in a call to .setAttribute(key, value) in the widget's default .initWidget. It is acceptable to destructively modify the atts parameter in one of your class's .init* functions if you do not want this to occur for some special attribute. The Widget class itself does not destructively modify the atts parameters.

By and large, this means that as long as you provide good defaults, you do not need much special initialization machinery; the default .initWidget is pretty good, and also correctly taps into the various special capabilities of .*Attribute.

setAttribute & set

.setAttribute(name, value)

Like the standard DOM method of the same name, but uses the resolution order described above.

.set(name, value) is a shortcut to this function. (I find the DOM to be rather verbose.)

getAttribute & get

.getAttribute(name)

Like the standard DOM method of the same name, but uses the resolution order described above.

.get(name) is a shortcut to this function. (I find the DOM to be rather verbose.)

Javascript Notes, Compatibility and OO
// if the call needs to have arguments massaged:
// where "arg1", etc. are replaced by your relevant arguments
Superclass.prototype.Method.call(this, arg1, arg2) // if it is safe to just pass all the arguments to the superclass
Superclass.prototype.Method.apply(this, arguments)
// in this case, "arguments" appears literally; it's a special variable
Inserting Widgets Into Your Documents

Inserting widgets into documents is a messy problem.

Many people think that the XBL solution, basically a hack on CSS to specify which tags get overridden by widgets, is messy and dangerous. Personally, I think that it's not such a bad solution to this particular problem, but it would be bad if everybody used this solution to implement their own extentsions; it's only a good idea as long as nobody really uses it. (Microsoft has provided example after example of the joys that come from sticking program-type information in stuff that should just be data, as CSS should be.)

In the meantime, we have to do something, and XBLinJS can't even take advantage of the CSS solution. On the upside, providing more than one solution doesn't bother me, and it is easy to create more, if you are willing to use browser-specific capabilities. (One favorite technology that at the moment is not cross-browser available, but would work well, is XPath.)

The best solution, if possible, is just to directly create Widgets from a Javascript fragment somewhere. It opens the door to truly dynamic, metadata-driven interfaces. However... most people don't think of Javascript in the way necessary to make this really pay off, and it can be inconvenient for various reasons.

widgets.js ships with a function named ReplaceWidgetTags, intended to be called from an onload handler in the document. It will walk the document DOM's tree, and every <widget> element it finds will be replaced with a widget of the type specified by the widgetType attribute. If the tag has an attribute "name", the resulting widget will be saved as WidgetGlobals.replacedWithWidget[name]. The remaining attributes on the tag will be passed as the attributes of the widget. So, for example, a tag in an HTML document like this:

<widget widgetType="LabelWidget" value="Hello!" name="label1">

will result in the equivalent of this call:

new LabelWidget({value: "Hello!", name: "label1"})

which will create a label widget and place it in the document, along with making the LabelWidget object available as WidgetGlobals.replacedWithWidget["label1"].

(See commentary in the advanced usage page about WidgetGlobals if you want to use XBLinJS in a multi-frame environment.)

The <widget> tags can be used recursively in Mozilla (IE support is planned for 0.6); when a Widget is created to replace a <widget> tag, all the child elements of tag will be called as a parameter to the newly-created Widget's .appendChild tag, except for trivial text nodes containing nothing but whitespace. Other widget tags in that sequence will be converted to widgets first. So, for instance,

<widget widgetType="Widget" name="root">hello! <widget widgetType="LabelWidget" > </widget></widget>

will be converted to a Widget, which after construction will recieve two .appendChild calls, one for a text node containing "hello! ", and one for a newly-constructed Label widget. The LabelWidget will never recieve an .appendChild call, because the internal text is trivial whitespace.

Note: In order for this to work correctly, you must close the widget tag with a full </widget>, unless you can guarantee the text will only be used in a full XML environment. By default, HTML browsers will assume each widget is a child of the previous one otherwise, which will mess things up.

Note: Some browsers lose the capitalization of the attribute names (for example Mozilla flattens everything to lowercase), so attribute names on the tags will all be lowercased, and any attributes you want to be able to use on a tag must also be all lowercase.

There isn't a wrong way to add the widgets, just ways that may not work in some browsers. If you are willing to do something browser-specific, a more efficient or powerful method may be available to you. Please consider sharing it with the project. You may wish to use the function replaceElementWithWidget(element), which takes an Element node from the DOM tree and replaces it with a widget as documented above. (Unless you're doing something really XML-based and weird, though, ReplaceWidgetTags is likely to be nearly optimal.)

appendTo

.appendTo(parent)

Appends the widget to the parent, which can be a DOM node or a Widget.

Jerf.org : Programs & Resources : Documentation: XBLinJS Foundation

 

Current Section Links

 

Sub-Sections

 

Search Jerf.org
Google

Search WWW
Search jerf.org