Wednesday, May 20, 2009

Dojo Templated, Part IIa

Just remembered this while writing the last post, but it merits its own post:

I mentioned earlier that the arguments to a widget that can be passed with attributes are limited to those that are already fields on the object. What I didn't realize then, and which has caused me a great deal of trouble previously, is that the parsing of those arguments depends on what the default value is!

Say that we have a widget like this:
dojo.declare("swadesh.languagePicker", [ dijit._Widget, dijit._Templated ], {
templatePath: dojo.moduleUrl('swadesh', 'templates/languagePicker.html'),
languages: [],
loadDelay: 10,
title: 'language',
hook: null,
postConstruct: function() {
console.log("Title: " + this.title + " hook: " + this.hook
+ " languages: " + this.languages.toSource() + " delay: " + this.loadDelay);
}
...


It allows four arguments to be passed with a dojoType style declaration, but they'll be handled very differently. The languages parameter is parsed as a list, so you can say '[1,2]' and it will be an actual list. The loadDelay is parsed as a number, so anything that doesn't parse as a number will become NaN. The title is parsed as a string, so no further evaluation is done. The hook is parsed as raw JavaScript and evaluated! Let's see this happen:
loadDelay="13.3" title="a.b(1+3)"
hook="'swadesh'.substr(3,3)">

When this HTML is loaded, it will give the following output on the console:
Title: a.b(1+3) hook: des languages: ["danish", "english"] delay: 13.3

It that not nifty? It would appear that the scope of the hook argument evaluation is the global Dojo scope, rather than the scope of, say, the object whose template is being evaluated. That might make sense just in terms of keeping track of who's initialized when, but it's a mite impractical to not be able to refer to one's own values.

I realize now that I ran into this several months ago and had a hell of a time figuring out why I got errors out of my string arguments. Now I know that had I specified an empty string instead of null as the default, it would just have worked. Tricky!

This little find is probably going to help me quite a bit here and there. Dojo is full of little surprises that seem odd at first, but turn out to be very powerful.

Dojo Templated, part II

Ok, so it's been a little slow in updating this, mainly because I've been busy using Dojo. I have continued working on the application that I showed a tiny bit of last time, which now has a name: LewenTrans (because it helps with transcription into IPA and calculation of Lewenstein distances).

Last time we saw the basics of using Dojo templates. It looked like a lot of work to just get a single variable into a template, but we only scratched the surface of what the Dojo Templated system can do.

Astute readers may have noticed that in the template, there was a div that looked like this:
  <div dojoAttachPoint="tipaDiv"></div>

This special attribute performs a small but extremely useful bit of magic: It assigns that DOM node to a field of that name in your widget object. Why is this so useful? Couldn't I just have used id=tipaDiv and document.getElementById() like normal humans?

Here we get to one of the main benefits of using Dojo Templated: It handles namespaces for you. Normally, when you make a web page, you'd have to take care to not use the same ID several times, or the browser will complain. Once you get into Javascript, you'll have to be even more careful to not define the same Javascript function twice, as the browser will not complain, merely call the last once whether you wanted it to or not. This is bad enough, but consider what happens once you start writing enough JavaScript that you want to reuse some of the code: Each bit of JavaScript you include might overwrite function or variables from other bits, and keeping track of this is a nightmare -- especially since you're not necessarily notified of this happening, things just go mysteriously wrong down the road. I can well imagine many people eschewing the use of Javascript just because of this "feature".

That hell of clashing namespaces is what Dojo Templates helps you with. Not just each class[1], but each instance of your class has its own namespace. Let's see how to do this. If I have a widget called swadesh.languagePicker that creates a little Ajax-based drop-down to pick a language, I can add it to the swadesh.tipaTable class by adding these two functions[2]:
postCreate: function() {
var lp = new swadesh.languagePicker(
{callback: dojo.hitch(this, "languageSelected")});
this.tipaDiv.appendChild(lp.domNode);
},
languageSelected: function(language) {
console.log("Got language " + language.toSource());
}
This not just creates a new languagePicker instance and plugs it into my HTML, it also gives that instance a callback function that can be called when the language has been selected. Let's take a look at the details:

The argument to languagePicker is an object, which on the languagePicker side is a set of keyword arguments. Customarily, the constructor uses dojo.mixin to add all the keys as fields on the instance. In this case, it's just the callback field, but it could be many others -- up to and including redefinitions of functions, if you feel foolhardy.

The dojo.hitch function makes a thunk out of an object and the name of a function. In this way, the languagePicker does not have to think about the fact that it should call the callback on another object, hitch encapsulates that. Very sneaky.

Next we see the use of the this.tipaDiv field which was assigned due to the dojoAttachPoint in the template. This is the separate namespace in action -- every time I make a new object, the tipaDiv field is a new part of the DOM tree. I can create as many as these as my poor browser will carry on its back, and they will all have each their little tipaDiv.

The template only gets processed once the constructor has been run (and the constructor could indeed create it on the fly if it doesn't require Ajax calls for it), which is why we use the automagically called postCreate function for setting this up. This function gets called once the template has been processed and it's all set up in the web page, ready for our manipulation.

The argument to appendChild is not the languagePicker object, since that object is not a DOM object, but something we have "handcrafted". However, the object has a field domNode that contains the instance of the template that is associated with our object. That one can be inserted in the DOM.

Now let's consider what would happen if we were to create two tipaTable objects (which I will need to do soon). Had these things been done with ordinary functions and id attributes, we would have had to mangle the ids and function names to keep them distinct. With Dojo Templated, all that is handled for us implicitly[3].

The computer savvy amongst you may already be thinking "why can't I use the dojoType attribute to embed this widget automagically from the template?" It's simple recursion, after all. You can, but there are a couple caveats:

First, you will need to tell the widget that there can be other widgets defined in the template, otherwise they won't get processed. This is done by adding a field
widgetsInTemplate: true,

to the widget whose templates contains other widgets.

Second, you might want to use Ajax to get some data to feed the inner widget, I do that on several occasions. Not so here though, so why didn't I use dojoType?

Because, third, you cannot (AFAICT) specify a hitched function in an attribute to the inner widget. Since I want a function on my object to be called, I need to create it by hand. Ways to fix this would be appreciated.

Now you have learned how to make Dojo widgets with JavaScript and how to use widgets within widgets, and also a very simple callback system. With this alone, you can do a fair amount of web page with not a lot of coding, but there's still more to come before we are truly Web 2.0, things like making Ajax calls, handling events and modifying the DOM tree. I'll get back to that.

[1] While Javascript don't have classes in the Java sense, the widget definitions function very much as such, and I will be referring to them as such.
[2] And adding dojo.require("swadesh.languagePicker") in the header.
[3] Though I wouldn't mind a shortcut for dojoAttachPoint, that's a long word to use all the time.

Friday, May 8, 2009

Encodings in Dojo, JSON and mod_python

Just had a hell of a time getting UTF-8 encoding to come all the way through using Dojo, JSON and mod_python. Here's how I ended up doing it:

To get correct encoding from a published python function:
def testJSONFromDB(req):
req.content_type = "application/json; charset=UTF-8"
cur = getCursor()
rc = cur.execute("select Orthography FROM Cognate WHERE CognateId = 609")
return req.write('/*' + simplejson.dumps(cur.fetchone()['Orthography'], ensure_ascii=False) + '*/');

The content-type was subtle; other response headers are set in a different way. The ensure_ascii bit is by default true -- which is annoying if you're trying to be international. (The DB statement is just to get one of the words that have non-ASCII values)

To read the encoding correctly on the Dojo side:
dojo.xhrGet({
handleAs: 'json-comment-filtered',
url: '/Python/swadesh.py/testJSONFromDB',
headers: {
"Content-Type": "text/plain; charset=utf-8"
},
load: function(data) {
document.getElementById('JSONFromDB').innerHTML = data;
}});

Here, the trick is that the headers should ask for text/plain in UTF-8, but the handleAs value should indicate JSON. Go fig. It works now.

Also, don't just the code tag when describing Python, it removes the syntactically important whitespace.