If you don't know what XBLinJS is, see the overview.
In this tutorial, we will build the beginning of an Image selection widget, until we run out of things to talk about in XBLinJS. If completed, it would allow the user to select an image somehow (typing a URL into a text box, perhaps), and see the image live, perhaps before submitting a form.
While this tutorial will work in both IE and Mozilla, along with any other browser that supports enought Javascript and DOM to run XBLinJS, I strongly recommend Mozilla for this tutorial, because it outputs much better error messages. When this tutorial shows output that varies due to browser differences, the Mozilla output will be shown.
To follow along, start by creating this basic HTML file, and putting it in the same directory as the XBLinJS source Javascript files and CSS files:
<html> <head> <link rel="stylesheet" type="text/css" href="widgets.css" /> <script src="jobject.js"></script> <script src="flavors.js"></script> <script src="widgets.js"></script> <script src="misc.js"></script> <script src="widgetJavascriptConsole.js"></script> <script src="widgetsBasic.js"></script> </head> <body onload="ReplaceWidgetTags(window)"> </body> </html>
This file is available as tutorial1.html.
This does nothing at the moment, but is the basic shell. In practice, those Javascript files will be all loaded into one file, but with this you can use the XBLinJS source files directly as downloaded.
Breaking it down line by line (other than the HTML lines which I assume you understand):
- <script src="jobject.js"></script>: Implements the extended object system that XBLinJS is built on. Right now, it isn't of much concern to you, but if you like the general object system XBLinJS presents, but don't want to use XBLinJS itself, you can take just this file.
- <script src="flavors.js"></script>: Defines the ability XBLinJS has to work under multiple situations, like in a normal HTML browser or in SVG in an XML browser. At the moment, this is also just foundation to you.
- <script src="widgets.js"></script>: Defines the basic machinery of the Widget class that we will explore here.
- <script src="misc.js"></script>: Provides some nice functions for debugging we will be referencing a few times here, but is not itself necessary. It's not really a part of XBLinJS and has some nice functions you may want to grab anyhow.
- <script src="widgetJavascriptConsole.js"></script>: Implements the Javascript Console, which we will invoke in a moment.
- onload="ReplaceWidgetTags(window)": Runs a function that implements the <widget> tag functionality.
Step one is to make it easy to debug. In the body of the HTML file, add a <widget> tag like this:
<widget widgetType="JavascriptConsole"></widget>
This file is available as tutorial2.html.
It is important to include the closing tag here, as we are in HTML mode and it is not sufficient to close the open tag with a slash (<tag />).
Now, pull open that file in your browser. Basic debugging:
- If nothing appears on the screen and there are Javascript errors, be sure all of the *.js files that shipped with XBLinJS are in that directory. (You don't need them all, but that's easier to check.)
- If you see a console on your screen, but it looks ugly and you can see the textbox, be sure you've put the widgets.css file in the directory with the tutorial file. It may not be a work of art, but it should be reasonably pleasing.
- If you still have problems, mail me and I'll try to help you.
And voila; you now have a Javascript console in the context of your development environment which you can play with.
However, that doesn't directly help us with our image selector. Open a <script> tag underneath the <widget> tag, and we'll get started.
Create a new widget class by calling deriveNewWidget.
deriveNewWidget("ImageSelector", Widget);
This creates a new Javascript class called ImageSelector, derived directly from the Widget class. (Changing that parameter will change the base class.)
Creating a new Widget is relatively complicated, so the deriveNewWidget function is provided. This creates a new widget which you can now add to the document. Underneath the JavascriptConsole widget, add a widget tag:
<widget widgetType="ImageSelector"></widget>
(The name ImageSelector is case sensitive.)
This file is available as tutorial3.html.
You can now view the page again. Underneath the Javascript Console, you should see what is basically an error message, starting with "This is the content of the Widget class". This is the default contents of a widget, which you didn't override. Since this is certainly not what you want, the default content is a helpful message. Let's correct that.
Adding content is unfortunately the dodgiest part of XBLinJS, but here's the basics: You define a Javascript Object that tells XBLinJS how to construct the DOM nodes that will make up the the Widget. The full details are available in the documentation, but a quick summary of the relevant attributes we will use:
- tagName determines the name of the HTML tag that will be created, i.e., p or ul.
- name is the name that will be used by XBLinJS to refer to this tag. (This is optional.)
- children: A Javascript array of further objects, describing the children this tag may have, in this same format. (Strings will be inserted as Text nodes.) This can be done recursively.
There are other elements, but we will describe them as we use them.
The attribute of ImageSelector that determines what is used as the content is, not surprisingly, the .content attribute on the prototype. So, after the deriveNewWidget call, add the following Javascript:
ImageSelector.prototype.content = { tagName: "p", children: ["This is our new content"], name: "paragraph" };
This is available as tutorial4.html.
Because the .content specification often gets long, I usually place it on multiple lines, even when it initially fits on one.
If you reload the file, you will see that the message has changed to match what you just created.
Before we add any more to our widget, let's take advantage of the Javascript Console for a moment and examine the Widget class we just defined.
We will represent input and output on the Javascript console like this:
> input output > input output
You already have one instance of your new widget on the page, but let's create a new one to play with:
> imageSelectorWidget = new ImageSelector() [ImageSelector WidgetGlobals.Widgets[5]]
The default toString method of the Widget class, which was implicitly called to generate that output string, tells you two things: The type of the Widget (ImageSelector), and how you can retrieve it if you want to manipulate it, which is really convenient for debugging. Try it:
> WidgetGlobals.Widgets[5] [ImageSelector WidgetGlobals.Widgets[5]]
Normally, you would not access a Widget via WidgetGlobals, but this is one instance where it's OK (debugging). WidgetGlobals is mostly used for XBLinJS bookkeeping, but if you're curious what is in there:
> display(WidgetGlobals)
(Note display is a function I wrote; it exists in misc.js.)
There isn't much of interest to you at the moment in there, but the display function can be useful.
You might wonder why your new ImageSelector was widget number 5, when you can only count two others on the screen, the Javascript Console and the other ImageSelector. The answer is that Widgets can contain other Widgets, and the Javascript Console contains some other Widgets, which you can peek at:
> display(WidgetGlobals.Widgets)
Widget 0 is an internal widget used for technical purposes; it does not appear in the DOM nodes of the page.
Widget 1 is the Javascript Console.
Widget 2 is a DOMWrap widget, which is wrapping the <input> element in the console so it has the various Widget services available to it. (Without this, the following Widget could not do its job.)
Widget 3 is the HistoryAddin, which is a Widget that implements the history for the Javascript Console, which you may not have noticed it has. After entering a few commands on the console, try pressing "UP".
Widget 4 is the ImageSelector that we added to the page with the <widget> tag.
Now that we have our new ImageSelector widget, we need to add it to the page. Note that imageSelectorInstance is an ImageSelector instance, not a DOM node:
> imageSelectorInstance instanceof Widget true
That means we can not directly use the .appendChild method of the normal DOM nodes, because DOM nodes won't understand what a Widget is. In Mozilla, for instance:
> document.body.appendChild(imageSelectorInstance) NS_ERROR_DOM_HIERARCHY_REQUEST_ERR: Node cannot be inserted at the specified point in the hierarchy
(That's a deceptive error message, as imageSelectorInstance isn't a "node" at all.)
The function DOM() is provided to get the DOM node out of the widget that can be added to the document. The DOM() function will return something that is already a DOM node unchanged, so it may be used safely to make sure you have a DOM node:
> DOM(document.body).appendChild(DOM(imageSelectorInstance))
By now you probably need to do some scrolling to see it, but there are now two instances of your prototypical ImageSelector widget on the bottom of the page. A couple of quick manipulations:
> imageSelectorInstance.hide()
This quickly hides the widget by setting the display of the top-level node to "none". It's the same as:
> DOM(imageSelectorInstance).style.display = "none"
And:
> imageSelectorInstance.show()
will make it re-appear; as you might expect, it is equivalent to
> DOM(imageSelectorInstance).style.display = ""
You may recall that I said the "name" is used by XBLinJS to access that node. One of the things XBLinJS will do is assign that attribute on the created Widget to the corresponding DOM node (or Widget, but more about that in a moment). So:
> imageSelectorInstance.paragraph [object HTMLParagraphElement]
This is, as it says, the standard DOM node:
> imageSelectorInstance.paragraph.style.backgroundColor = "red"
turns the background red, just as you'd expect.
Now that you've worked with the basic debugging, back to the main event:
If we're going to have an image selector, sooner or later we're going to need an image tag. Change your content to read as follows:
ImageSelector.prototype.content = { tagName: "div", children: [ {tagName: "p", children: ["Choose your image: "]}, {tagName: "img", src: "one.jpg", name: "image"} ] };
Also, add the attribute globalname="selector" to the ImageSelector widget tag; this will give that widget the global name "selector", which you can see in the Javascript console widget:
> selector [ImageSelector Widgets.WidgetGlobals[4]]
This is available as tutorial5.html.
Notice in the content specification for the <img> widget, we have a src: "one.jpg" entry. Anything XBLinJS doesn't understand in the content entry is passed along to the DOM node (or widget) via .setAttribute. In this case, we set the source of the image to start out pointing at one.jpg.
We could manipulate the source of the image by accessing selector.image.src all the time, but that reveals more about the implementation of the widget to the outside than we should. It also doesn't give us a chance to hook into the setting process, which we will want to use in a bit. We'd much rather say something like selector.set("imageSource", "newSource.jpg"), as if this were one widget.
XBLinJS Widgets (actually, JObjects) use a generalized .set[Attribute] and .get[Attribute] interface, and provide several ways to hook into that interface as you write Widgets, depending on your needs.
The simplest way is to use inheritance, allowing the inner widget to "inherit" the setting of the outer widget. Change the content line for the <img> tag to:
{tagName: "img", src: "one.jpg", name: "image", inherits: "src"}
This is available as tutorial6.html.
Now, in the Javascript widget:
> selector.get("src") .../one.jpg
(The exact content of the ... will vary, depending on your situation.)
> selector.set("src", "two.jpg")
And the image changes.
In more complicated cases, you may want to rename the inner attribute; suppose you had two <img> tags inside, and you want to be able to change the src independently. (It will "work" to have both inherit src, but they will then both always be set to the same thing, which isn't what you want.) You can do this:
inherits: "src=imageSrc"}
This is available as tutorial7.html.
And then:
> selector.set("imageSrc", "two.jpg")
will reset the image.
I sometimes have trouble remembering the order, so the mnemonic to help remember is that the attribute getting set on the inner element ("src" in this case) is on the left, like a conventional variable assignment.
Inheritance works in the simple case, but gives you no opportunity to perform any logic on the setting or getting.
"inherits" is a frequently used idea, but a weak one. It's much nicer to have "properties". Ideally, you have a situation where obj.property can fire a method call. Javascript supports that in some environments... but not IE.
To make up for that somewhat, XBLinJS allows you to define methods on your object with a name of the form get_X and set_X. XBLinJS will call those functions when you use obj.get("X") or obj.set("X", "value").
(Actually, XBLinJS supports real Javascript properties in environments that support them, but you can only use them if you commit to those environments, which as of this writing means writing off IE, which most people can't do.)
As it turns out, as nice as it is to set the image directly, we really want to capture the setting of the image, because we don't want to just display the entire thing inline. We want to display a "thumbnail". (It's not a real thumbnail because you still have to load the entire image, but it's as close as we can come online without server support. In some ways, it's not all bad anyhow, as you can verify directly that it is the true image.) We can accomplish this by add a .set_imageSrc method. We'll let the default .get implementation just return the .imageSrc attribute, so we don't have to write that method.
Get methods are called with no parameters, and set methods are of course called with the parameter being set.
First, verify that your new method is called:
ImageSelector.prototype.set_imageSrc = function (value) { alert(value); }
Remove the inherits statement in the content, as you don't need it anymore.
Note we have to use ImageSelector.prototype.set_imageSrc = function (value) and not function ImageSelector.set_imageSrc (value), because Mozilla does not support the latter syntax. If you can commit to IE, feel free to use the latter. (To make the typing shorter, you can do something like is = ImageSelector.prototype, and use is.set_imageSrc = function (value). I choose not to do this because it messes up my documentation system.)
Pull that up, and .set the imageSrc to something. You should get what you set alerted.
> selector.set("imageSrc", "something")
Of course, the image doesn't change anymore, and we should fix that. Replace the alert with something to set the src of the image. Remember, the image is named image:
this.image.src = value;
We need to be able to get the source of the image back out, too. You have two basic choices... you could store the value in this.imageSrc, and the default behavior of the .get method will retrieve that if you call it.
this.imageSrc = value;
Or, you could write a .get_imageSrc method to retrieve it directly from the image on demand. I tend to prefer the latter, because it minimizes the possibility that something will get out of sync. It also means you get the full URL, not just what you set the URL too, which is useful behavior we will exploit later.
ImageSelector.prototype.get_imageSrc = function () { return this.image.src; }
This is available as tutorial8.html.
If you have get_* or set_* functions you use repeatly, you should factor them out into a "Variable", which is a sort of composite variable on a Widget that you can program how it behaves. This makes more sense than copying and pasting the same basic code, over and over. Variables can take parameters in their definition; see the docs for more information, or some of the sample code.
(This isn't a very good name, but I haven't come up with a better one.)
If you call .set[Attribute] or .get[Attribute], and none of these features are used with that name, Widgets will fall back to setting and getting the attribute on the widget object itself. Thus, to future proof your code, you can, if you choose, use .get or .set to access attributes of the Widget that you may want to use the *Attribute machinery on later.
What use would a widget library be without event handling?
At the moment, we are in a sort of transition period between two event models for web pages. The original event model is that each element can have one event handler, and the handler is called as appropriate. The 4.x series of browsers introduced some idea of "event bubbling", which was rather poorly defined.
XHTML inherits its behavior from the DOM Events specification. In that model, you add "listeners" to nodes for given events, and there can be any number of listeners for the same event. There are no order guarantees for the order of the listeners called.
Despite the fact the latter should only be in XHTML documents, for some value of "should", it is often available even in pure HTML documents, and the interaction can be interesting if you use the both together.
Both models have their weaknesses; standard HTML can't have multiple things looking at the same event. This may not seem like that big a deal, but it leads to a natural implementation of some things. For instance, the Javascript console you use on the page has a History Widget that independently implements the History behavior. It gets first crack at the keypress events, and only acts if it sees a keypress it "wants", like the Up Arrow. If it does, it eats the event, otherwise, it passes it on. This is often quite useful.
DOM events, for reasons that I don't quite follow, don't guarantee the order the handlers listeners will be called, which also makes this behavior impossible.
XBLinJS implements its own event routing system on top of either of these systems, as long as you don't interfere by adding more listeners or resetting the handler XBLinJS sets. It allows both multiple handlers for one event, and guarantees they will be called in reverse order of registration (most useful).
This is an appropriate time to discuss it in terms of our lil' project, because when the users changes the image, we really would like to capture some information about it, so we can size it correctly. However, changing the image might require a trip out to the network, and the browser doesn't let your code wait for that. Fortunately, it does fire a load event on the image when the image loads in. (We're going to ignore the case where the image fails to load for the purpose of this tutorial.) We need to capture that event and handle our sizing then.
The easiest way to capture an event in XBLinJS is to create a specially-named method, named after the event you want to capture. In the simplest case, when you want to handle that event on the top-level DOM node, or you want to allow it to bubble up to that point, you can just name it after the event, prefixed with "on" (like in an HTML tag). In this case, we want to capture it on a specific sub-element of the Widget, which we can do by adding an underscore, then the name of the thing we want to add the handler to.
We'll start once again by simply proving that the function we create is being called:
ImageSelector.prototype.onload_image = function (event) { display(this.image); }
This is available as tutorial9.html.
Now, when you load the page, you get a dialog box pretty quickly showing the details of the image element. If you reset the image source...
> selector.set("imageSrc", "two.jpg")
you'll get another dialog box, and note it's filled with the info for the new image, not the old one.
You can of course capture any event that the browser will let you capture.
Advanced event handling is beyond the scope of this tutorial, but if you study the behavior of the History Widget, you can learn a lot about how one Widget can tie into the events of another cleanly. You can implement "plug-in" Widgets like the History Widget, or, if you need it, use that Widget yourself. You can also see how to register events manually.
The logic needed to get the images to show up as thumbnails is boring and not relevant to this tutorial. Therefore, to continue this tutorial, and to make sure you are in sync with the tutorial, pull tutorial10.html out of the tutorial directory to continue.
This already contains the basic logic to shrink the image to thumbnail sizes, if necessary.
When creating a widget in code, you pass the desired attributes in as a Javascript object as the first parameter of the new invocation:
var imageSelector = new ImageSelector({imageSrc: "one.jpg"})
In order when using HTML to construct the widgets, you can pass attributes on the HTML tag itself. These attributes are passed through the standard get_*/set_* system,
Note in tutorial10.html, the thumbnail logic looks up this.maxsize to see what the max size ought to be. You can change this in the HTML tag that invokes the ImageSelector widget:
<widget widgetType="ImageSelector" globalname="selector" maxsize='100'></widget>
No explicit code is needed for that; a .set("maxsize", "100") call is made during Widget creation, and since nothing was defined especially for the maxsize parameter, it fell through to the object itself.
Obviously, this is not yet a complete widget, but the remainder now is applying what you already have learned. To complete the widget, you would need to:
- Add some sort of input field for the user. It could be a textbox, but you might also provide a dropdown if you have a limited number of choices. If you are ambitious, you can create a widget that provides a drop-down for common answers but provides a text box for new ones. Connect a method to the "onchange" event for your input events and use that to drive the "imageSrc".
- Provide some way to get the input back out. There are two basic ways to do this, synthesize a form submission, or include a hidden element in the Widget that can be used in a form. (Note that a Widget for synthesizing a form submission entirely from scratch ships with XBLinJS.)
You could also:
- Wrap the image in another div, sized to 75x75 constantly so as the image changes, the page doesn't keep reformatting.
- Put a border on that div.
- Provide the user with an indication of the size of the image.
- Allow a user (developer) to set the size of the thumbnail, and have the image automatically adjust. You can simplify this by making one set_thumbnailsize function that can handle both the initialization and the setting. (This technique comes in handy a lot; it maximizes both the utility of the widget and the simplicity of writing it when you unify attribute setting and initialization.)
But I figure you'd rather use the library to solve your own problems than watch me putter through this one.