Monday, 19 May 2008

Old-School Dijit helpers

Being raised on dojo 0.4.x there were a few widget things that seem to have been lost along the way for dojo 1.x. This may be due to better practice etc but old habits die hard.

var widget = {
byId: function(/*String*/ id){
return dijit.byId(id);
},
all: function(){
var widgetArray = [];
var widgets = dijit.registry._hash;
for(var a in widgets){ widgetArray.push(widgets[a]); }
return widgetArray;
},
allHash: function(){
return dijit.registry._hash;
},
byType: function(/*String*/ type){
type = type.toLowerCase();
var widgets = this.all();
var widgetArray = dojo.filter(widgets, function(item){
return (item.declaredClass.toLowerCase() == type);
});
return widgetArray;
}
}


Main ones here are core.widget.all() that returns all present widgets and core.widget.byType() that can fetch by declared widget class.

"all" is handy for debugging some times just to make sure all your widgets are appearing on the page and sometimes to see if dynamic widgets are being created.

"byType" isn't used as much but some times can be handy to grab numerous widgets of the same type that aren't related like buttons etc.

There are likely to be newer (smarter) ways of doing this kind of stuff. But it was a quick hack to get an old approach back.

Sunday, 18 May 2008

Evolved coding with Dojo - No more string trickery

Nothing like a bit of code in action to get a sense of how JavaScript as a language is coming along.

So to get things rolling, I've come up against the situation where there are several rows of a form and each one starts with a checkbox. Clicking that checkbox enables or disables the form elements in that row restricting access to that row and making sure the user is aware of what they are changing when they do it.

I've often seen how my hardcore java programmer colleagues approach this. They usually hardcode an onclick with node id trickery to find what to enable and disable. Server side they increment id's and hope they match as the page changes. Just one of many possible approaches here.

function update(obj){
var id = obj.getAttribute("id");
var new_id = id+"_element";
var node = document.getElementById(new_id);
node.disable = obj.checked?'':true;
}


We can do better than this though.

For starters, anyone who's seen dojo a little bit will know about dojo.connect. This means we don't need to hard-code onclicks, can be smarter about memory leaks (crucially with IE) and connect anything to anything.

Step one, when the page loads we want all the checkboxes on the page to magically gain an onclick event. I could search for all checkboxes in general but don't want to accidentally clash with form input checkboxes. So to make this easy I'm going to give all the checkboxes I want the style class 'checkboxTrigger'.


dojo.addOnLoad(function(){
dojo.forEach(dojo.query(".checkboxTrigger"), function(item){
dojo.connect(item, "onclick", updateStatus);
});
})


The function does pretty much what was said... On load, query for elements with a class 'checkboxTrigger'. For each of those, connect the updateStatus function.

Next we need the update function.

The update function should figure out what checkbox was clicked. Then find all the input fields in that row, except for checkbox triggers, then set the disabled attribute on all of the remaining form elements.

var updateStatus = function(/*Event*/ e){
var targetNode = node = e.target;
while(node.tagName.toLowerCase()!="tr"){ node = node.parentNode; }
var inputs = dojo.filter(dojo.query("input", node), function(item){
return (item.className!="checkboxTrigger");
});
var state = targetNode.checked?'':true;
dojo.forEach(inputs, function(item){ item.disabled=state; });
}


So here we find the target of our click event which should be the checkbox. Walk up the dom tree a few steps to find the "tr" row node. You could use "div" if you wrap your nodes in a div (being politically correct). From there we use query to find inputs in that row, filter out checkboxes with our special class name and then use forEach to loop through and set a disabled status on those fields.

Presto. A function that can generically enable or disable fields in a row using trigger checkboxes. We didn't care how many rows there are or how many fields in the row and no trickery with id's. This means generic code with less chance of failing to find nodes and killing your code with exceptions.

There's plenty you could do to this final function to make it more robust, more capable of finding form elements etc, but it's a place to start.

Monday, 14 April 2008

Why Not - Code highlighting with Dojox

I recently stumbled across files in dojox for code highlighting and I thought why not? why not add a bit of syntactical sugar to blogger. So making use of AOL's very generous cross domain dojo hosting, I gave it a go.

You'll find info on the AOL CDN dojo releases here: http://dev.aol.com/dojo

From there we need to include the dojo core in our template using bloggers template html editor and add a tiny bit of script that looks something like this:

<script djConfig='parseOnLoad: true' src='http://o.aolcdn.com/dojo/1.1.0/dojo/dojo.xd.js' type='text/javascript'/>
<script>
dojo.require("dojox.highlight.languages.javascript");
dojo.addOnLoad(function(){
dojo.query("code").forEach(dojox.highlight.init);
});
</script>


which means when the page is finished loading, grab all the "code" blocks using dojo.query and then loop over them initialising them with dojox.highlight.init.

This is pretty much lifted from the highlight test case and is worth a look if you want to see more examples.

At this point, a little tweak to the css with the block

pre code {
display: block;
border:1px dashed #aaaaaa;
overflow:auto;
background-color:#ffffff;
color:#444444;
padding:3px;
whitespace:pre;
}

..and we have code blocks that are highlighted and will handle scrollable overflow.

For some reason, IE currently doesn't obey the 'pre' aspect of all of this and hence white spacing is lost. I'll look into that when I get a chance.

Tuesday, 18 March 2008

Templating made nicer with Jaxer, dijit and dojo.query

Once upon a time dojo had markup ability using custom tags. This was pulled because as it turns out, some browsers just scrap tags they don't understand. One browser that did support tags of a non HTML nature though was Mozilla and in turn we get this freedom back via Jaxer.

So in this knowledge, I did a quick concept mock-up. And in essence the gain can be varying depending on the amount of work you want the back end to do.

In it's simplest form, we can change

<div dojoType="dijit.form.button" label="test"></div>

into
<dijit dojoType="dijit.form.button" label="test"></dijit>

and simply do a server-side rebadging of the nodes so our gain is mostly readability of tags.

But I wanted something a little more exciting. I've been working with JSF and JSTL on and off and am pretty familiar with the notion of a tag, render/component definition and tag definition in a config file. What this gives us is a splitting of code, presentation and config.

So following that blueprint I created a tag definition that looks something like this:

tags: {       
dijit:{
button: {
include: "dijit.form.Button",
dojoType: "dijit.form.Button",
passthrough:['label', 'iconClass', 'showLabel'],
passthroughContent: true
},
numberspinner: {
include: "dijit.form.NumberSpinner",
dojoType: "dijit.form.NumberSpinner",
passthrough:['constraints', 'value']
}
}
}


Here we have a reference to what include is needed, the dojoType and any passthrough tag attributes we might need. The passthrough is really an optional concept but I kind of like making safe the information that gets taken from the tag and pushed to the widget.

Next how to call it. Simply put:

<dijit type="button" label="test2"></dijit>


And lastly...the processor. Well usually we kick things off with a dojo.addOnLoad but that's not applicable in Jaxer land (at the moment) so instead we start things using a connect and the "onserverload" event.

The processor does the following:
  1. Loop over the the tag definition object grabbing the first level child nodes. These nodes are the actual tag names to search for so in our case we have "dijit".
  2. Use dojo.query to find every instance of custom tags like "dijit" and add them to a queue.
  3. Loop over the queue, first checking each node for content that has a dijit tag in it. If so add it to the back of the queue so that we process from inside content to outside parent tags.
  4. Continuing with the node, use its type to find the definition in the tag definition set and convert the node into a div tag with a dojoType attribute.
  5. Pull over a base set of attributes like id, name, style and then the pass through attributes.
  6. Add the specified require to an object for later.
  7. If necessary, pull content of the dijit tag over to the div tag.
  8. When all nodes are processed, loop over the require object building up a series of dojo.require statements.
  9. Add the require statements to a script block in the page and make sure it's set to runat='client'
With this process we convert all nodes to the expected divs and neatly contain all dojo.requires in one place. This means we can now create a simple reference in a custom tag to a more elaborate template configuration and backend generation. It really feels like an evolution of JSTL in some ways.

Does it work? My proof of concept code says yes and I've taken it further so that instead of replacing dijit tags with divs we replace it with programmatic instantiation. This idea is even better in some ways as it takes some of the heavy lifting away from the parser. The client-side only has to worry about code execution and not only that we can use the backend to accumulate code and keep it tidy just as we did with accumulating the dojo.requires and then outputting them in one place. Having said that I've been really impressed at recent optimizations with the parser.

It's worth noting that this as always is a "what if" experiment and should not be taken as perfect or an enterprise solution

From my quick trials one of the key advantages with Jaxer has been the ability for the server to keep track of various page level pieces of information and then arrange them neatly for the client-side. The fact that it's Mozilla-like at the back end effortlessly supporting the required features is really refreshing.

Tuesday, 4 March 2008

Jaxer + Dojo - Progress

Some really great progress has been made by the dojo guys, particularly J. Burke.

Catch some of the discussion on the trac thread:

http://trac.dojotoolkit.org/ticket/5878


There's been some discussion about the callback state of Jaxer and this interests me mostly because I'm curious and kind of like the idea of functional dojo everywhere. I've started a ticket looking into why Dojo and callbacks are different (and not working) in comparison to the working JQuery examples.

http://trac.dojotoolkit.org/ticket/6066

Monday, 25 February 2008

Dojo + Iframes

There's been a lot of talk about getting dojo to work with Iframes but I think that statement has left room for confusion. To get dojo to work with iframes, there are two possible scenarios.

The first is that we want to load dojo into the parent frame and use it to access child frames. This is presuming the child frame is simple content and only needs to be modified with things like dojo.byId.

The other scenario is the one that I am more concerned with and that is dojo is loaded both into the parent and the child iframe. This is because we want to use things like dijit widgets and other advanced rendering ideas in both the parent and the child windows.

Scenario 1 - Child frame = Simple content

For the first case where we only want to access information in the child window, tools such as dojo.setContext and dojo.withGlobal are perfect as they temporarily swap dojo's context to act as if it were within that child window. From there all the calls to finding dom nodes etc are performed locally in that child window. Be careful though as these methods swap the context of the global object so all dojo related commands are performed within the current context. If you leave the context locked to a child frame and don't swap it back in time for some parent code to run, things can behave badly.

As far as I know you can't reliably create dijit widgets in a child iframe whilst keeping all the dojo code hosted in the parent frame. I'd be really happy if someone told me I was wrong on this.

Scenario 2 - Child frame = Dojo content

For the second scenario we load dojo code twice (first in the parent and then in the child iframe window) but we do have the advantage that dojo is allowed to work independently of other documents. No worries about concurrently running code and context sharing / swapping. Other things like dijit don't have to worry about sharing manager code cross-window either.

Apart from the heavy lifting of the code there is one other downside to a dojo in each frame as pointed out by JPSykes in his entry. There is a siloing effect between the frames as they don't register with one another or communicate with one another.

Speeding up the load

Given that I want to load dojo in each frame to gain full dojo/dijit functionality I found a way of making the code loading experience a little smoother. When we initialise a page with iframes we load the parent window and then it's dojo resource, then repeat for the child window and it's dojo resource. Since we are loading dojo twice I wondered could I leverage what the parent window knows of the dojo code?

Looking at the mechanics of dojo, dojo loads code with dojo.require and that is a very elaborate (and really fantastic) wrapper to the native xml http request object. That means underneath it all, we are still just getting text and evaluating it into the page. This is what caught my attention. If you dig deep enough through the dojo.load methods you'll find a method called dojo._loadUri.

dojo._loadUri uses _getText to retrieve a string of the code and drop it into the page. My theory was, what if we don't just discard that string but rather add it to a storage object and then get a child frame when it calls dojo.require to ask the parent "have you already got this?". If it does, instead of making a remote request, we can grab it off the storage node, hopefully speeding things up.

When you step back from the technical side, this becomes an in-page caching system using dojo.require as the interface to that data cache. So is it possible? The following I store in a file called parentCache.js and load directly after loading dojo.js:

var codeRepository = {};    
if(typeof(djConfig.noParentCache)=="undefined"){
dojo._loadUri = function(/*String (URL)*/uri, /*Function?*/cb){
if(this._loadedUrls[uri]){ return true; }
var topWindow = window.top;
if((topWindow)&&(topWindow != window.self)){
// attempt to load in the parent window
topWindow.dojo._loadUri(uri);
// make sure we have a storage node
if(topWindow.codeRepository){
// load from parent
var contents = topWindow.codeRepository[uri];
}
if(!contents){
//not present, try loading manually locally.
var contents = this._getText(uri, true);
}
} else {
//we are the parent, load it manually.
var contents = this._getText(uri, true);
}
if(!contents){ return false; }
codeRepository[uri] = contents;
this._loadedUrls[uri] = true;
this._loadedUrls.push(uri);
if(cb){ contents = '('+contents+')'; }
var value = dojo["eval"](contents+"\r\n//@ sourceURL="+uri);
if(cb){ cb(value); }
topWindow = null;
return true;
}
} else {
console.log("------------------------ no parentCache ----------------------------");
}


Note. Yes, I'm sure you can make this more compact or streamlined.

By keeping this separate from dojo I don't have to worry about re-editing dojo code when we upgrade. Looking at the code, it overrides the loadUri method with the new ability as described above. It is also wrapped in a check that checks djConfig for the noParentCache flag. If that is set to true then we don't worry about this caching idea. This flag is very helpful for development as without it, if we change code and try to reload the iframe, it'll grab the cached copy off the parent again without picking up the saved file changes.

The real catch for this is that you don't create monolithic dojo build files anymore. Loading everything up with a single dojo.js means only a minor advantage for the iframe caching as we still need to load a base dojo.js file to start things. Finding a balance between how much of an initial load you do and how much you allow to cache for child frames is a bit of a guessing game. What it does mean though is that child frames no longer care how fast or reliable your internet connection is once you start getting cached info off the parent.

Again I don't claim this to be perfect, the "best way" or Dojo compliant. It's just exploring ideas.

Overcoming the Silo

How we approach cross iframe communication really depends on the requirements. If we want to broadcast a message to all frames then we can use dojos fantastic topic / publish code to push a message around. We can even create wrappers that loop over each frame and use withGlobal just as mentioned in discussions found here.

The thing I've found is that it's often easiest to use window.top to always find the root parent window and work from there. Whether it's pushing information into a common storage point or cascading a message or action back down through the frames.

Caution

From all this cross frame tinkering, there are known memory leak conditions under IE that can occur with window and variable references if not cleaned up. If you are communicating between windows, try and keep the information that is passed between them, contained and clean it up when the function is finished. Iframe memory leaking with the dojo object seems to occur under IE no matter what I do but some minor things like stray variable references can have massive impacts on how much is leaked.

Thursday, 21 February 2008

D + J - Simple Tree Test

Simple test case grabbing a file list of the dojo dir (one child deep) and pushing that into a dijit.tree widget.

Disclaimer: I'm in no way claiming this to be the "right" way of doing things with Dojo or Jaxer. This is just some experimenting.



<html>
<head>
<style type="text/css">
@import "dijit/themes/dijit.css";
@import "dijit/themes/tundra/tree.css";
@import "dojo/resources/dojo.css";
@import "dijit/tests/css/dijitTests.css";
</style>
</head>
<body class="tundra">
<script runat="client" src="dojo/dojo.js"></script>
<script runat="server">
//dojo compatability block
Jaxer.load("dojoCompat.js");
function oncallback(){
Jaxer.load("dojoCompat.js");
dojo = window.dojo;
}
</script>
<script runat="server">
function getFolderInfo(pathStr, level){
var maxLevel = 1; //hard coded limiter for quick test case
if(!pathStr){ pathStr = "dojo"; }
if(!level){ level = 0; }
var dir = new Jaxer.Dir(Jaxer.Dir.resolvePath(pathStr));
var files = dir.readDir();
var fileNames = [];
try{
dojo.forEach(files,function(item, index, data){
var name = item.getLeaf();
var info = {
"id":("node"+index),
"name":name,
"isDir":item.isDir(),
"isFile":item.isFile()
};
if(item.isDir()){
if(level == maxLevel){
info.children = [];
} else {
var newPath = (pathStr+"/"+name);
info.children = getFolderInfo(newPath,(level+1));
}
}
fileNames.push(info);
});
} catch(e){}
return fileNames;
};

function getData(){
return dojo.toJson(getFolderInfo());
}
getData.proxy = true;
</script>
<script runat="client">
dojo.require("dijit.Tree");
dojo.require("dojox.data.jsonPathStore");
dojo.require("dojo.parser");

function buildTree(){
var dirStructure = dojo.fromJson(getData());
var data = new dojox.data.jsonPathStore({
data:dirStructure,
idAttribute:"id",
labelAttribute:"name"})
var tree = new dijit.Tree({
store:data,
label:"root",
labelAttr:"name",
somePropertyAttr:"name",
query:{query: '$[*]'}});
dojo.byId('placeholder').appendChild(tree.domNode);
}
dojo.addOnLoad(buildTree);
</script>
<div id="placeholder"></div>
</body>
</html>