Archive for the ‘JavaScript’ Category

Tree Rendering in Client-Side, No Recursion

Monday, February 7th, 2011

I once built a nested message board (using ASP & VBScript, bah). In order to print all messages, nested, I created a recursive function that queries the DB for all messages of a certain parent, prints the messages and calls the same function itself with parent ID of the current message. That was a disaster to the server, as a single page could execute about 100 queries.

I decided to move the rendering logic to the client side, since SEO wasn’t such a big of a deal for this, and the load time was critical. So at first I queried the messages I wanted (I could do this in one query thanks to the DB structure), I pushed the data to JavaScript, as a flat, unordered array of objects, using JSON:

var treeData=[
	{ "id":1,	"parentId":null,  "title":"1"			},
	{ "id":2,	"parentId":1,     "title":"1.1"			},
	{ "id":3,	"parentId":null,  "title":"2"			},
	{ "id":4,	"parentId":1,     "title":"1.2"			},
	{ "id":5,	"parentId":1,     "title":"1.3"			},
	{ "id":6,	"parentId":4,     "title":"1.2.1"		},
	{ "id":7,	"parentId":4,     "title":"1.2.2"		},
	{ "id":8,	"parentId":4,     "title":"1.2.3"		},
	{ "id":9,	"parentId":null,  "title":"3"			},
	{ "id":10,	"parentId":9,     "title":"3.1"			},
	{ "id":11,	"parentId":10,    "title":"3.1.1"		},
	{ "id":12,	"parentId":11,    "title":"3.1.1.1"		},
	{ "id":13,	"parentId":11,    "title":"3.1.1.2"		},
	{ "id":14,	"parentId":11,    "title":"3.1.1.3"		},
	{ "id":15,	"parentId":13,    "title":"3.1.1.2.1"	}
];

The first approach to print this, nested, is using a recursive function in JavaScript, that, on every call needs to find the child messages based on their parentId, and actually mimic the server behavior (loop on root messages, query child messages, print, recursively).

But a way cooler approach, is to utilize the DOM itself for nesting, and to utilize a hash table indexer to store and find messages based on their id.

After creating all elements for all messages, a hash table (associative array, or just a JavaScript “object”) can be used to store a node based on its id, so when we want to find it, we can just use nodesById[someNodeId]:

var nodesById={};
for (var i=0;i<nodeCollection.length;i++) {
    nodesById[nodeCollection[i].data.id]=nodeCollection[i];
}

Then, the magic of having all elements nested happens when looping on all nodes. Each node has a parent_id except root nodes. We need to find the parent of current node in the nodesById object, by using nodesById[nodeParentId], and append the current node to its parent we found. Root nodes appended to a root container.

for (var i=0;i<nodeCollection.length;i++) {
    var node=nodeCollection[i];
    var parentElement=node.data.parentId ? nodesById[node.data.parentId].getChildrenElement() : rootNodeItemsContainer;
    parentElement.appendChild(node.element);
}

That way, we utilize the DOM for native nesting, without a recursion, and a simple single loop. The array doesn’t even have to be ordered, and no worries about requesting a node when its parent isn’t exist, as all of the elements are already created before.

The Entire Code

var treeData=[
	{"id":1,"parentId":null,"title":"1"},
	{"id":2,"parentId":1,"title":"1.1"},
	{"id":3,"parentId":null,"title":"2"},
	{"id":4,"parentId":1,"title":"1.2"},
	{"id":5,"parentId":1,"title":"1.3"},
	{"id":6,"parentId":4,"title":"1.2.1"},
	{"id":7,"parentId":4,"title":"1.2.2"},
	{"id":8,"parentId":4,"title":"1.2.3"},
	{"id":9,"parentId":null,"title":"3"},
	{"id":10,"parentId":9,"title":"3.1"},
	{"id":11,"parentId":10,"title":"3.1.1"},
	{"id":12,"parentId":11,"title":"3.1.1.1"},
	{"id":13,"parentId":11,"title":"3.1.1.2"},
	{"id":14,"parentId":11,"title":"3.1.1.3"},
	{"id":15,"parentId":13,"title":"3.1.1.2.1"}
];
function NodeItem(data) {
	this.data=data;
	var container=document.createElement("li"),
		title=container.appendChild(document.createElement("div"));
	title.innerHTML=this.data.title;
	this.element=container;
} NodeItem.prototype={
	// lazy children element creation - not all nodes have children
	getChildrenElement:function () {
		return this._childElement = this._childElement || this.element.appendChild(document.createElement("ul"));
	}
};
// convert all nodes to NodeItem instance
var nodeCollection=treeData.map(function (node) { return new NodeItem(node); });
// for faster lookup, store all nodes by their id
var nodesById={};
for (var i=0;i<nodeCollection.length;i++) nodesById[nodeCollection[i].data.id]=nodeCollection[i];
var rootNodeItemsContainer=document.createElement("ul");
// the magic happens here:
// every node finds its parent (by the id), and it's being adopted by the parent's children element
// that, actually, builds the tree, before it's in the document
// all root nodes are appended to a root container which is appended to an element on the document
for (var i=0;i<nodeCollection.length;i++) {
	var node=nodeCollection[i];
	var parentElement=node.data.parentId ? nodesById[node.data.parentId].getChildrenElement() : rootNodeItemsContainer;
	parentElement.appendChild(node.element);
}
document.body.appendChild(rootNodeItemsContainer);

JavaScript Gotcha: Semicolon and Self Executed Function

Sunday, November 7th, 2010

Wasted a couple of “What the hell is wrong here?” moments about this code:

function doSomething() {}
doSomething()
(function () {
	// self executed
})();

Runtime error: doSomething() is not a function.

Obviously, I missed a semicolon after the doSomething() call, but I didn’t imagine this caused the error.

JS usually forgives about missing semicolons, if next statement is not in the same line. However, having the parenthesis of the self executed function made JS parse code as

doSomething()(function (){})();

Which expects the returned value from doSomething to be a function, that is executed with the anonymous function as a first argument.

So in conclusion, never forget semicolons. Ever. Even when just debugging code.

JavaScript Tip: Remove ‘falsy’ Items Out of an Array

Monday, November 1st, 2010

The new ECMAScript 5 brought a lot of enhancements that we used to see only on JavaScript frameworks. The Array class now has methods like forEach, map & filter, which are very useful.

If you need to support older browsers, which you probably do, this filtering method can be also found in MooTools (Array#filter), jQuery ($.grep) and other JS frameworks.

A quick tip to remove all falsy (false, null, undefined, 0, NaN or an empty string) items out of an array:

var a=[1,2,"b",0,{},"",NaN,3,undefined,null,5];
var b=a.filter(Boolean); // [1,2,"b",{},3,5]

Since Boolean constructor is also a function, it returns either true for ‘truthy’ argument or false for ‘falsy’ argument.

For example:

Boolean(0); // false
Boolean(true); // true
Boolean(1); // true
Boolean(""); // false
Boolean("false"); // true. "false" is a regular, non-empty string

And writing

b=a.filter(Boolean);

is actually the same as writing:

b=a.filter(function (x) { return Boolean(x); });

Asynchronous JavaScript Queue/Sequence

Friday, October 8th, 2010

What are Asynchronous Functions?

An asynchronous function is a function whose completion depends on the actions of another object. The program in which it runs does not wait for the object to return a result, rather, that object “pushes” the result when it’s finished. This push comes through the callback function – a function defined specifically for the purpose of receiving a result, and that object calls the function with arguments it supplies. The signature of the callback function contains these arguments, and uses them in order to complete the procedure.

For example: asynchronous use of XMLHTTP. Upon sending the request to the server, the other scripts keep running while the page continues functioning. The XMLHTTP object has an event named onreadystatechange which is called each time the server returns an answer. This is unlike the synchronous case, where the browser waits for a response from the server. The window ‘freezes’ and only comes back to life when the response is received. This behavior is simply not Internet-correct, and not user-friendly.

Usually when we have an asynchronous function, we’d like another function to run upon its completion. We’ll put the call to the second function in the callback of the first one.

If we have a large number of functions and callback, they start to depend on each other: when A terminates call B, when B terminates call C, and so on. If I pull B out of the sequence, I’ve broken the chain, because C will not be called.

Case of Asynchronous Functions in Sequence

As an example, we’ll look at this sequence:

  1. Performing a semi-transparency effect on an element to be displayed, and once complete:
  2. A button appears in the element, when clicked:
  3. An XMLHTTP request is sent to the server, and when a response is received:
  4. A message is displayed, alerting that the process has finished.

The division into functions is quite simple:

function effectElement() {
	var div=document.getElementById("div");
	var opacity=0;
	var iv=setInterval(function () {
		opacity+=.05;
		div.style.opacity=opacity;
		if (opacity>=1) {
			clearTimeout(iv);
			showButton();
		}
	},50);
}
function showButton() {
	var button=document.getElementById("button");
	button.style.display="";
	button.addEventListener("click",function () {
		requestServer();
	},false);
}
function requestServer() {
	var xh=new XMLHttpRequest();
	xh.open("GET","url.htm",true); // true - async call
	xh.onreadystatechange=function () {
		if (xh.readyState==4) {
			div.innerHTML=xh.responseText;
			finish();
		}
	};
	xh.send(null);
}
function finish() {
	alert("finish");
}
window.addEventListener("load",function () {
	effectElement();
},false);

We see here that we can call the next function within each function’s callback. But what would happen if we were to remove one of the functions from the sequence? We’d have to change the function containing the call to the removed function, to call another function. And what would happen if we wanted to change the order? A mess.

Generic Solution for a Sequence of Asynchronous Functions

I’ve tried to think of a generic solution to the problem, a solution which would let me decide which function comes after which with minimum code and maximum flexibility, and where each function calls the next without being dependent on it.

This is the basic syntax:

var sequence=new Devign.Sequence();
sequence.add(effectElement);
sequence.add(showButton);
sequence.add(requestServer);
sequence.add(finish);
sequence.start();

Within each callback, the current sequence should be advanced by:

currSequence.next();

where currSequence is passed with the function as a single argument.

Simple, isn’t it?

We can play with the order of the functions, add and remove as much as we want.

Devign.Sequence Class

// Devign.Sequence class
if (typeof(Devign)=="undefined") var Devign={};
Devign.Sequence=function () {
	// private fields
	this.list=[]; // a list of functions
	this.index=-1;
	this.aborted=false;
	// public fields
	this.finished=false;
};
// public methods
Devign.Sequence.prototype={
	// adds a new function
	add:function (sequenceFunction) {
		this.list.push(sequenceFunction);
	},
	// starts the sequence from the first function
	// fires 'onStart' if exists
	start:function () {
		this.index=-1;
		this.aborted=false;
		this.next();
		if (typeof(this.onStart)=="function") this.onStart();
	},
	// ends the sequence
	// fires 'onEnd' if exists
	end:function () {
		if (typeof(this.onEnd)=="function") this.onEnd();
		this.finished=true;
	},
	// proceeds the sequence
	// if the sequence has finished calls 'end'
	next:function () {
		// if sequence was aborted - ignore next statements
		if (this.aborted) return;
		this.index++;
		if (this.index==this.list.length) return this.end();
		var currFunction=this.list[this.index];
		// calls the function with the sequence as an argument
		if (currFunction) currFunction(this);
	},
	// aborts the sequence by setting the index to 'not started' and the flag aborted to true
	abort:function () {
		this.index=-1;
		this.aborted=true;
	}
};

Each instance of the class has an array of functions, to which we can add more functions with the method add. Furthermore, there is numerical field named index which tracks the index of the function currently being executed (with -1 meaning the sequence has not yet begun).
For each instance we can also define two events: onStart and onEnd. If defined, they will run at the appropriate time.
Each time we move forward in the sequence, the index is incremented and the next function is called from the array. The function is called with the parameter this which refers to the instance of the class. The reference to the class is sent so that it may be identified from within the function, and advanced with next.

Complete Code

<script type="text/javascript" src="Devign.Sequence.js"></script>
<script type="text/javascript">
var sequence=new Devign.Sequence();
sequence.add(effectElement);
sequence.add(showButton);
sequence.add(requestServer);
sequence.add(finish);
window.addEventListener("load",function () {
    sequence.start();
},false);
function effectElement(currSequence) {
    var div=document.getElementById("div");
    var opacity=0;
    var iv=setInterval(function () {
        opacity+=.05;
        div.style.opacity=opacity;
        if (opacity>=1) {
            clearTimeout(iv);
            currSequence.next();
        }
    },50);
}
function showButton(currSequence) {
    var button=document.getElementById("button");
    button.style.display="";
    button.addEventListener("click",function (e) {
        currSequence.next();
        button.disabled=true;
        button.value="Wait...";
        e.preventDefault();
    },false);
}
function requestServer(currSequence) {
    var div=document.getElementById("div");
    var xh=new XMLHttpRequest();
    xh.open("POST","/ajax_html_echo/",true); // true – async call
    xh.onreadystatechange=function () {
        if (xh.readyState==4) {
            div.innerHTML=xh.responseText;
            currSequence.next();
        }
    };
    xh.send("html=Ajax completed");
}
function finish(currSequence) {
    alert("finish");
    currSequence.next();
}​
</script>
<div id="div" style="opacity:0;height:100px;width:100px;border:1px solid #000;background-color:#32C3D7;">
	<input type="button" id="button" value="Click" style="display:none"/>
</div>

MooTools toElement Method and ToElement Mixin

Tuesday, September 28th, 2010

The toElement Method

Classes like Calendar, Slider, AutoCompleter etc. are all containing elements or responsible for elements.

There are several approaches for how to handle the element in the class:

  • Pass a container element as an argument to the constructor of the class, and append to it new elements that the class creates
  • Have the actual behavior-less element(s) on the HTML and pass them to the class constructor as an argument, the class will apply its behaviors on them
  • Create all elements in the class and have them available for appending

Each approach is good for a different situation, but the toElement method is meant mostly for the last one.

Example of a plain class, and how its element is being appended to the document:

var AutoCompleter=new Class({
	initialize:function () {
		this.input=new Element("input");
		// autocomplete magic
	}
});
var autoCompleter=new AutoCompleter();
$("container").grab(autoCompleter.input);

This is quite ugly to use the public property input, which its name can be changed someday and then the code breaks. Moreover, in this way, each class declares its own property and this can lead to inconsistency between the property names.

This is where the toElement method comes in handy: it’s a MooTools convention for a method that simply returns the element.

var AutoCompleter=new Class({
	initialize:function () {
		this.input=new Element("input");
		// autocomplete magic
	},
	toElement:function () {
		return this.input;
	}
});
var autoCompleter=new AutoCompleter();
$("container").grab(autoCompleter.toElement());

But wait, there’s more: MooTools $ / document.id function knows that if it gets an object that has the toElement method, the method should be invoked and the returned element will be returned out of the document.id function. Luckily, all of the MooTools Element methods use document.id inside them, so, for instance, when grab or adopt are called with any object as an argument, it will be passed thru the document.id function which will translate it to the toElement result. Hence, we can use this:

$("container").grab(autoCompleter);

The ToElement Mixin

The ToElement Mixin is supposed to make the toElement method even simpler, and make it lazy-load the element, i.e. create it only when needed.

var ToElement=new Class({
	ui:{},
	toElement$ensureElement:function () {
		var element=this.toElement$getElement();
		if (!element && this.build) this.build();
	},
	toElement$getElement:function () {
		return (this.ui ? this.ui.element : this.element) || this.element;
	},
	toElement:function () {
		this.toElement$ensureElement();
		return this.toElement$getElement();
	}
});

Usage:

var AutoCompleter=new Class({
	Implements:[ToElement],
	initialize:function () {
	},
	build:function () {
		this.ui.element=new Element("input");
		// autocomplete magic
	}
});
var autoCompleter=new AutoCompleter();
// element isn't ready yet
// the grab calls document.id which calls toElement, where the element is prepared
$("container").grab(autoCompleter);

First, it adds a ui hash to every class instance. That’s my convention to save elements in classes of this kind. Second, it adds a toElement method, which is responsible of calling the build method if exists, and return this.ui.element or this.element out of the instance.

Benefits are:

  • Lazy load the element – it’ll be created only when the document asks for it
  • ui hash to contain all other elements, and a build method as a simple convention