Programming is a complicated subject and I can't cover all the bases for how you may wish to use this library. But here are some tips and tricks for more advanced uses.
In some advanced scenarios, it is desirable the main widgets.js files in a foreign frame.
Among the advantages are not reloading the .js files on every page access, regardless of the randomness of the browser cache, and the avoidance of potentially expensive re-initialization and general slowdown from executing large, redundant chunks of code.
However, the library is used this way by the author so it should be able to be made to work. The keys are:
- Every client frame calls createWidgetGlobals() and assigns the result to WidgetGlobals: WidgetGlobals = createWidgetGlobals(). Every client document is processed with fixDocumentTagConstants(document) for IE. (IE doesn't include the DOM constants to identify node types, but XBLinJS needs them.)
- Every widget created includes in its atts a parameter named window containing the window object that the Widget will be used in. The globals used in that widget will be drawn from that window. (Unfortunately, it is impossible even in theory to automate this in widgets.js; those constructors will always get a reference to their window if they use the window identifier.) Note that Widget methods run in the context they are defined, and the definitions are in that foreign window, so you'll need to use this.window to access the window the widget is in. (In practice, this doesn't come up much because you're usually working with the nodes the widget owns, which will automatically work correctly.)
- You need to reach into the containing window to get the widgets themselves. I wrote a "frame.js" that is loaded in every frame, and copies relevant entries from the top-level frame into the current one. There are some other things that can be convenient to copy over, depending on your code.
Don't mix the widget declaration amoung various windows or the WidgetNameToType registry will break.
You need to be extra careful to access the globals through "this.globals" and not "WidgetGlobals"; single-frame users can get away with the latter, you can not. If WidgetGlobals appears, that's probably a bug.
If you do this all correctly, you should end up with the result being that all of the persistent widget code stays in the frame, and all of the transient stuff you want garbage collected, like Widget instances, are attached to the changing HTML page, so everything pretty much just garbage collects naturally and memory leaks won't be XBLinJS's fault. It is worth periodically checking WidgetGlobals.Widgets (I use display(WidgetGlobals.Widgets), display is in misc.js) in the various frames to be sure that widgets are where they belong; I find it easy to forget to pass in the window parameter and that will cause Widgets to migrate into the containing frame. Fortunately, this is generally easy to fix.
Also be sure not to load widgets.js into other windows, as the separate definitions of everything causes problems; a Widget constructed from one definition won't be an instanceof the other Widget definition, which breaks some XBLinJS code. In particular, if DOM(widget) keeps returning the Widget, and not a DOM node, this is the most likely reason.
The Widgets shipped with XBLinJS are just the ones I've done that I think people may find generally useful; I've also built several real-world systems with this. There are several things I've learned.
First, it is advantageous to keep the .init* functions as small as possible. It is not possible to completely eliminate them in all cases, but the more you can shift into .defaults, various .set_* functions, and the rest of the .setAttribute system, the better. This eliminates a lot of conditional "did I get this parameter?" logic. It also generally results in widgets that are easier to use from within other widgets by just using .set(), which makes things much easier.
Do be sure to clearly document whether a .set_* function is capable of recieving a parameter multiple times; I often find that the ones I write for initialization only can't be called more than once. (Perhaps I should provide an easy way to indicate that so the errors can be caught earlier?)
Would you like to inherit a .content from a parent widget in such a way that changes will propogate, but you don't want it to be quite the same? Consider this cute trick:
deriveNewWidget("ChildWidget", ParentWidget); ChildWidget.prototype.content = ( (c = objCopy(ParentWidget.prototype.content), c.someParam = "newValue", c) );
Recall that objCopy only copies one level deep; you may need to write a deeper copy function if you don't want to change a surface attribute.
XBLinJS ships in such a way to make it easiest to use with cross-browser HTML environments. Using DOM in an XML environment that supports DOM2 and XML namespaces, as Mozilla's XUL does, requires significant changes, most notably by using the namespace-aware node creation functions like createElementNS or setAttributeNS.
XBLinJS ships with the easy ability to switch over to using these calls. Do one of the following:
- In your copy of widgets.js, change the FLAVOR = BrowserFlavor line to read FLAVOR = DOM2Flavor.
- In between the inclusion of flavors.js and widgets.js, add a line setting FLAVOR = DOM2Flavor, as before.
This results in a Widget class that is aware of namespaces and such, and allows you to specify defaultURIs and mnemonic->URI mappings (i.e., telling XBLinJS what "html:" means), and specifying tag names just as you would in conventional XBL. The primary difference is that you must explicitly tell XBLinJS what the namespaces are since it can't hook into XML mechanisms. (In practice this is not a problem, and XBLinJS ships with specifications for html:, xul:, and defaults to the XUL namespace, so most current Mozilla apps probably won't need to do any specification at all.)
See the DOM2Flavor class documentation for more about the relevant changes.
Like the general setup of Widgets, with .setAttribute and stuff, and you want to use it outside of Widgets? Good news, that's all factored out into a class called JObject, which basically has all the yummy goodness of the Widget data model without any of the DOM manipulation code. See jobject.js and the documentation.