(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
- Requirements
- XBL Feature Comparison
- Installing XBLinJS
- What Is A Widget?
- Deriving From "Widget"
- Getting A Widget Instance and Adding It To The Document
- Widget Content
- .initData(atts), .initDOM(atts) and .initWidget(atts) (Widget Initialization)
- Methods
- Event Handling
- Properties, "Variables", and Data Handling
- Javascript Notes, Compatibility and OO
- Inserting Widgets Into Your Documents
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:
- Know what Javascript is
- Have at least some idea about the Object Orientation capabilities of Javascript, but you need not be an expert. (In this documentation, I will use the term "class" for a function meant to define a Javascript object prototype, and talk of "deriving" subclasses, as if Javascript were a conventional class-based language and not the prototype language that it is. It is convenient to be able to use the terms "class" and "derive" to talk about the intention of the relevant actions. Where appropriate, the project does take advantage of the prototype nature of Javascript.)
- Have progressed sufficiently in web development to know how to place Javascript files on a server and use <script> tags to load them into web pages without any assistance from me.
- Know something about using DOM in Javascript in a browser
XBLinJS only has a chance of working in a sufficiently strong Javascript interpreter. The interpreter:
- Must support Javascript exceptions (try/catch/throw)
- Must support .call and .apply on Function objects
And the browser must support at least DOM 1, which should be everything current.
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:
- The general idea of an XBL widget binding.
- Behavior inheritance, via Javascript subclassing. (Must be done manually, more annoying but more flexible and more capable.)
- Ability to specify content of the widget, both HTML tags and other Widgets. (Clumsier syntax, but with the support of a full Turing-complete language; several useful enhancements.)
- inherits support. Redirection via value=someotheratt is supported. Inheriting "everything else" supported via "*".
- Property support. (Through .set[Attribute] and .get[Attribute], which is clumsier, but you can use magic methods .set_[name] and .get_[name] to catch those. You can do it directly with Javascript Properties at the cost of cross-platform compatibility by flipping the TRY_TO_CREATE_PROPERTIES variable in widgets.js, but that is only useful under certain limited situations as IE does not support that.)
- Event handler support. (Automatically through specially named methods, or manually. Event model richer than XBL's by default, allows event chains on a single object as well as single handlers. Cascading as supported by the browsers.)
- Children support; also, using the provided tag replacement scheme you can embed XBLinJS widget tags in other XBLinJS widget tags (currently only in Mozilla). There can be multiple insertion points. At the moment, there is no support built-in for selecting child nodes and moving them amoungst various insertion points, but you can override Widget.appendChild to do anything you want with child widgets.
Kludged
- Support for easily embedding widgets into documents. While CSS isn't the best solution, it cleaner than anything available cross-browser. However, some utility functions are provided to run as "onload" handlers, and the way is pointed to creating any necessary custom code.
Not Implemented
- The entity support granted automatically by XML; this is so trivial to do in Javascript and pure string-replacement mechanisms generally so useless anyhow that I leave it to you to implement what your app needs. (For instance, pure lookup is inadequate for localization.)
Added
- Via the .defaults member of the class definition, explicit support for default parameters that hook into the full .set/getAttribute system.
- Cross-platform support; XBLinJS works in HTML browsers (Mozilla and IE at least), as an XBL replacement with XUL, and (possibly with minor tweaks) anywhere DOM2 and the DOM2 event model is used.
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.
A "widget" consists of the following things, which will be examined in detail in the following sections:
- A new Javascript class somehow derived from the "Widget" superclass.
- A definition for the "content" member on the prototype which will define what DOM nodes the widget consists of. (This is equivalent to the content tag in XBL, and is the clumsiest conversion at the moment since we don't want to depend on XML parsers in the browsers, and we do want to take advantage of Javascript's expression capabilities.)
- An object member named .defaults, declaring any default arguments.
- An initializer method to set up any extra DOM data, named "initDOM".
- An initializer method to set up any extra instance data, named "initWidget". These two initializers are equivalent to the constructor tag in XBL, thought it is more flexible.
- A series of methods as appropriate to your widget. (Like implementation in XBL.)
- A series of specially named methods which will be automatically registered as event handlers. (Equivalent to handlers in XBL.)
- Optionally, some specially-named methods which comes as close as cross-platform-ly possible to emulating properties.
Finally, we need some way to insert the widgets into your document, which is an ugly problem, but is solvable in several ways.
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(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 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))
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.
The Widget library will add the following attributes to each widget object instance:
- .widgetID: A unique ID number for the created widget. This is also the key into the Widgets object to retrieve a reference to the widget. This is critical for the event handling system, so don't change or delete this.
- .domNode: The top-level DOM node that represents the widget. If you completely override the default widget Content support (as documented in the next section), you must set this manually in your .initDOM override.
- .rootWidget will point to the root widget. A widget can define itself as containing other widgets; the root widget will have a reference to itself in .rootWidget and the contained widgets will have a reference to the widget containing them. This is used only for advanced applications.
- And the following private members used to implement various services that you need not worry about, but should not overwrite: appendTargets, inherits, events, vars.
- Finally, not on the widget but on the DOM node that represents it, the attribute widget will be set on the Javascript object representing the DOM node (i.e., domNode.widget = widget, not domNode.setAttribute("widget", widget). (As of this writing I'm not sure if this will work as intended in all browsers, but it is working in all the ones I've tried it in.)
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:
- tagName: This determines the type of HTML node that will be created. This should be a valid HTML tag name. Either this or "widgetType" must appear; they both can not be used for the same object.
- widgetType: This should either be a string identifying the widget type in the WidgetNameToType object (done for all Widgets created via deriveNewWidget), or a reference to the Widget class. Either this or "tagName" must be present; they both can not be used for the same object.
- name: This determines the attribute name on the resulting Widget object for this node, as shown above. Unlike the rest of these special attributes, this one actually will be passed to an HTML node via .setAttribute(), though it will not be passed to a sub-Widget.
- children: A Javascript array containing either: Further Javascript objects conforming to this definition, strings which will be created as Text Nodes and inserted into the resulting DOM tree, or additional arrays, which will be treated as if they are flat. (That is, [child1, child2, child3] is equivalent to [[child1, [child2, child3]]]. This may sound wierd, but when you need it, you'll be glad it is there; it is particularly useful when using functions to create the .children.) Children that result in "false" or "undefined" being returned will not be added to the parent object. (This can only come up if you have made an error in your object definition or a .createObjectCustom returns it; most of the time this will not happen.)
- appendTarget: A string that gives a name for an .appendChild target. The special name "default" indicates the default target, which is used when no target is given to the .appendChild method of Widgets. (Widget's .appendChild, as documented below, works like the standard DOM .appendChild, but can handle appending Widgets, and takes a target as an optional second parameter.)
- inherits: Like inherits in XBL, a comma-separated list of attributes to inherit. If multiple subwidgets will need to use the same attribute, you can use the redirection syntax from XBL: "internalName=externalName". The "internalName" is the name that will be passed to the subwidget when the .setAttribute call is made, and the "externalName" is the name that users of the top-level widget will use to access the attribute. (I have a hard time remembering the order, so I use this as a mnemonic: for "innerVar = outerVar", it works just like an variable assignment. The value of "outerVar", the name belonging to the "outer Widget", will be assigned to "innerVar" on the "inner Widget".)
In addition, on the end of the list (or as the only member), you may specify a "*" to indicate "everything else"; you can still use redirection syntax to redirect certain attribute names, although it doesn't make any sense to list non-redirected names with "*", as they will be included. Example: inherits: "value=personName,*". This makes it fairly easy to make wrapper Widgets that delegate all attribute access down to the thing they are wrapping. Note: The default Widget.initWidget (see below) calls .setAttribute(name, value) on all leftover attributes passed to the constructor. Inheriting with * will make it so .setAttribute never makes it down to the Widget object itself, so add it to existing Widgets with care. (New Widgets will probably be fine.)
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:
- 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.
- 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; }
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(element, target)
Adds the DOM node or widget element to the target append point. "default" is used as the target if none is given.
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.
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(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) {}.
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.)
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(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").
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(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(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.)
- For a prototype named "jsObject", IE allows adding methods to that prototype(/class) with the syntax function jsObject.method (args) {body}. Mozilla does not. For maximal compatibility, use the syntax jsObject.prototype.method = function (args) {body}.
- If your constructors are just assigning constants to various things, don't forget that you can assign those things to the .prototype.
- The syntax for calling a superclass method correctly, given that the sub-class is named Class, the superclass Superclass, and the desired method Method, is as follows, depending on your needs:
// 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 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(parent)
Appends the widget to the parent, which can be a DOM node or a Widget.