Posts Tagged ‘dom’
Tree Rendering in Client-Side, No Recursion
Monday, February 7th, 2011I 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
MooTools toElement Method and ToElement Mixin
Tuesday, September 28th, 2010The 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
uihash to contain all other elements, and abuildmethod as a simple convention
JS/DOM AHA Moment: getElementsByTagName Collection is Always Up to Date According to the Current DOM Tree
Tuesday, September 7th, 2010Today I found out something interesting about getElementsByTagName. This method is quite popular, it returns all elements of a requested tag under a specific parent element.
The type which is returned out of a getElementsByTagName call is NodeList, which is not exactly an Array, but is enumerable and has the length property.
When you call document.getElementsByTagName("div") you get a NodeList of all div tags, but it seems that every time you use this variable, it shows an up to date list.
Meaning – if you have a NodeList instance of all divs on the document and then you dynamically add more divs to the document, this instance will contain all the new divs as well. Same for removed divs.
Test Case:
How to Maintain the Original Collection?
Simply, by creating a new Array out of the NodeList‘s contents:
var divs=document.getElementsByTagName("div");
divs=Array.prototype.slice.call(divs);
// the above is a shortcut to:
// var tmp=[];
// for (var i=0,l=divs.length;i<l;i++) tmp.push(divs[i]);
// divs=tmp;
MooTools, jQuery, Prototype etc. do not return a NodeList out of their DOM selector engines. They return an Array instance, so no need to worry about this.
This also applies to getElementsByClassName and getElementsByName, but not to querySelectorAll.
I investigated a bit inside the WebKit’s source and it seems like that the getElementsByTagName / getElementsByClassName / getElementsByName methods return a DynamicNodeList while the querySelectorAll method returns StaticNodeList.
JavaScript ‘this’ Keyword
Monday, August 2nd, 2010Definition of the JavaScript this Keyword – The object which called a function.
The JavaScript this keyword is used within any function scope or the global scope, and in each scope it receives a different value. The value of this inside a function, effectively depends on the object which called it.
What exactly is scope? Every function gets its own space in order to define variables and properties. When we want to access a certain variable, we must do it through the space in which it was defined. In JavaScript, when we haven’t defined a function, everything happens in global scope.
Code
var o={}; // declaring new object
o.name="moon";
o.method=function () {
alert(this.name);
};
o.method();
We’ve defined a new object named o and a method which displays this.name. But where does this come from?
By calling o.method(), we’ll get the value "moon". The process that’s taking place is the following:
- We create a variable of type string with the value
"moon"and assign it to the name property of objecto. - We create a function with certain content and assign it as a method of
o. - We call the function so that
ois the object that calls it. - The function is called when
thisis actually a reference too. We geto.namewhich is"moon".
It is important to note that a function is a variable in every sense. It contains expressions which we can call later on. Having this function assigned to o’s property “method” doesn’t connects the this variable inside it to o. The object doesn’t know about the contents of this function. The actual assignment of this only happens when the function is called, when this is translated as the object that called it, hence, o.
From here, we can make it a slightly more sophisticated:
var o={}; // declaring new object
o.name="moon";
o.method=function () {
alert(this.name);
};
var x={};
x.name="sun";
x.method=o.method;
x.method();
We’ve declared a new object named x with a name property set to "sun".
We’ve put a reference to the method we defined in o into x.method as well. When calling x.method, we won’t get, god forbid, "moon", because there is no actual assignment to o. We get "sun" because x is the object that called the function, and the value of this is set to the object which called the function.
We can say that every JavaScript function has a hidden argument named this. Furthermore, with each call to the function, the argument is passed behind the scenes with the pointer to the object which called the function.
function f() { alert(this.name); }
Becomes:
function f(this) { alert(this.name); }
Meaning:
var o={}; // declaring new object
o.name="moon";
o.method=function (this) {
alert(this.name);
};
o.method(o);
var x={};
x.name="sun";
x.method=o.method;
x.method(x);
In C++, the compiler adds an argument named this to every method in a class, which is an object of the class type, and effectively every call to a method becomes a call to the method together with that variable (please ignore C++ wrong syntax):
class Foo {
string name="foo";
string GetName() { return this.name; }
}
Foo o=new Foo();
string s=o.GetName();
Actually becomes something like:
class Foo {
string name="foo";
string GetName(Foo this) { return this.name; }
}
Foo o=new Foo();
string s=o.GetName(o);
More on this in C++ on Wikipedia
Changing the Value of this
It is possible to change the value of this, not by assignment (you cannot assign a value to this), but rather by calling a function via another object, as we did with x – defining the function as a property of an object and calling the function through it.
JavaScript gives us the tools to call a function with a different this without too much effort, with the help of the methods call and apply. These two methods are quite similar: they are methods of a function (defined in Function.prototype) that take an object that should be the this and arguments, and call the function itself with these arguments. The difference is in how the arguments are passed – call takes regular arguments (like params in C#), and apply takes the arguments as an array.
We’ll change the former code to work with call:
var o={}; // declaring new object
o.name="moon";
o.method=function () {
alert(this.name);
};
var x={};
x.name="sun";
o.method.call(x);
What happens behind the scenes, sort of:
x.temporaryMethod=o.method; x.temporaryMethod();
this and new Operators
A JavaScript function is not just a group of statements for later use, it also serves as a class; every function is also a class. We can create instances of a class.
function Foo(name) {
this.name=name;
}
This function, like all functions, is also a class; we create an instance of it thus:
var instance=new Foo("bar");
alert(instance.name); // "bar"
What is actually happening behind the scenes? When we call new Foo("bar"):
var instance={}; // declaring new, empty object
Foo.call(instance,name); // calling the constructor with the new object as this, and the argument "name"
Calling the constructor will call the expression:
this.name=name; // this is the "instance" object. assign name as the sent argument
The compiler creates a new, empty object and activates the class’ constructor with call. This way, this will be a reference to the newly created object. The constructor calls the expressions and the references to this that it contains, and then new object is returned.
this and prototype
The prototype property is a class property, and whichever value we assign it will be available to all instances of the class. When accessing an object property, the existence of the property in the object will be checked first and, if not found, will be checked in the prototype of the class to which the object belongs (with Object being the basic class).
In JavaScript there are a number of pre-defined classes:
Object Function String Number Array Date Error RegExp
We can extend any class with our own functions, and so each instance of the class will contain our extension.
C# contains a feature called extension method which extends the functionality of built-in classes such as int and string. This feature is also found in Ruby.
String.prototype.trim=function () {
return this.replace(/^\s+|\s+$/g,"");
};
var s=" x ";
alert("<"+s+">");
alert("<"+s.trim()+">");
After extending the prototype of the class String with a property named trim which is a function that trims blank spaces from the beginning and end of the string, we can access this method from any string. this in the function will contain the value of the string, because the string variable is the object that called the function.
this and the DOM
<div id="div" onclick="alert(this.innerHTML);">div</div>
The DOM knows to interpret the code thus:
div.onclick=function () {
alert(this.innerHTML);
};
From here it looks familiar; onclick is defined as a property whose value is a function with use of this.
The browser will apply a “listener” for mouse clicks. Once a click is received, it checks whether its location is on this div. If so, it checks whether the div has an onclick attribute, and then just calls the function like this:
div.onclick(event);
Thus, this contains a reference to the div.
Summary
this is a wide world in JavaScript. Comprehensive understanding of it will allow us to write shorter, more efficient and advanced code.
OhBehave – Apply Behavior to Static/Dynamic Elements, Immediately
Wednesday, June 23rd, 2010Applying a behavior to an element is a very common task in web applications and rich web sites. Something like an element onload event that is called whenever it’s ready, regardless of whether it’s pre-created in the html, or dynamically added.
For example:
- Converting a dropdown list (<select> tag) to an AutoComplete/custom designed select control
- Converting input type=checkbox/radio into images
- Wrapping an element with a complex frame (rounded corners, shadows, opacity)
- A floating label on a textbox and hiding it on a value change or focus
The Common, Bad Solution
A standard solution will be waiting until the DOM is ready (a function that exists in every self-respecting JS library), finding all elements with a certain class and calling a JS function on that element that will attach the behavior.
Disadvantages of this solution:
- Sometimes, it takes a little while until the DOM is ready, and meanwhile, the element appears in its original, behavior-less form
- When elements are dynamically added to the DOM (innerHTML / createElement & appendChild) – the behavior must be applied again on all new elements, manually.
- A generic solution for dynamic elements is having an interval in which looks for these elements. However, this lookup is slow and the delay may cause flickering.
Code Sample for the Bad Solution:
function applyBehaviors() {
var elements=$.select(".behavior");
for (var i=0;i<elements.length;i++) applySpecificBehavior(elements[i]);
}
$.domready(function () {
applyBehaviors();
});
$.ajax("url.html",{
onSuccess:function () {
applyBehaviors();
}
});
Or, an interval:
setInterval(function () {
var elements=$.select(".behavior");
for (var i=0;i<elements.length;i++) {
if (elements[i]._behaviorApplied) continue;
applySpecificBehavior(elements[i]);
elements[i]._behaviorApplied=true;
}
},200);
(The functions $.select, $.domready and $.ajax can be found in any JS library).
The Desired Solution – OhBehave
OhBehave is a neat script that is responsible for applying behaviors to elements immediately, as soon as they are available, without waiting for the rest of the DOM to load. In addition, it also applies the behavior on new dynamic elements.
You can create new elements on the fly or set an element’s innerHTML with new html tags, without worrying about attaching behaviors. An <input type=”checkbox”> can be converted to image base on its .checked property as soon as it’s appended to the document.
If you use Ajax to get HTML from server, you shouldn’t be worried about wiring up events to elements.
As Event Delegation is good for attaching events to all elements of a certain type, OhBehave does the same for more complex behaviors.
How does it work?
For every browser I had to find a different solution, as each of which implements the desired functionality differently. Each of the implementations is a small browser-specific code that calls OhBehave.initialize, which receives an element and applies all behaviors needed.
The behaviors for that element are defined in its class attribute, each of which is prefixed with “oh-behave-” (optional). E.g. oh-behave-AutoComplete, oh-behave-WrapWithFrame.
Example for a behavior
OhBehave.behaviors["AlertTheTime"]=function (element) {
element.onclick=function () {
alert(new Date());
};
};
// or, as a global function -- function AlertTheTime(element) { ... }Implementations
Firefox (Gecko Engine) – XBL
XBL, or – XML Binding Language, is a markup language, based on XML, that is defined according to a W3C standard and is used for applying rich behavior to an element. XBL allows us to declare properties (including getters/setters), custom events (and to fire them), custom methods and custom style. The XBL is placed on an element using css:
.oh-behave { -moz-binding:url("oh-behave.xml#oh-behave"); }The XML structure:
<bindings xmlns="http://www.mozilla.org/xbl" xmlns:html="http://www.w3.org/1999/xhtml">
<binding id="oh-behave">
<implementation>
<constructor>OhBehave.initialize(this);</constructor>
</implementation>
</binding>
</bindings>As for now, only the Gecko engine supports XBL, even though it’s a standard.
IE (Trident Engine) – HTC
HTC, or – HTML Component is Microsoft’s implementation for applying rich behavior to an element. It supports more or less the same as XBL.
Safari/Chrome (WebKit Engine) and Opera (Presto Engine) – The Event DOMNodeInserted
As WebKit/Presto didn’t include XBL yet (there are some mentions in the source code but it’s still turned off probably), I had to find a different solution.
The DOMNodeInserted event (a W3C standard) is fired any time an element is inserted dynamically (innerHTML / createElement & appendChild). Through this event I look for elements that should have the behavior. The lookup is done with querySelectorAll – a method of a document or element that gets a CSS selector and returns all elements for that selector.
For elements that are already in the document, I used the DOMContentLoaded event, which may cause a small flick but the engine is so fast that I barely believe it’s noticeable.
Code
// behavior example - assigns alert of the current time onclick
OhBehave.behaviors["AlertTheTime"]=function (element) {
element.onclick=function () {
alert(new Date());
};
};
// behavior example - adds a frame around an element
OhBehave.behaviors["WrapWithFrame"]=function (element) {
var frame=document.createElement("div");
frame.className="cornerized";
element.parentNode.replaceChild(frame,element);
frame.appendChild(element);
var corners=["tl","tr","bl","br"];
for (var i=corners.length;i--;) {
var corner=document.createElement("div");
corner.className="c "+corners[i];
frame.appendChild(corner);
}
};
<div class="oh-behave oh-behave-WrapWithFrame oh-behave-AlertTheTime">Auto apply behavior on existing element</div>
When the div appears on the screen, it obtains immediately the onclick event and being wrapped with a box with 4 corners.
Demo
In the demo page there are two behaviors applied on same element: 1) Wraps the element with a frame and some other elements, 2) Attaches a click event to show current date.
In addition, there’s a button that adds more elements, dynamically and the behaviors applied automagically.
Demo of OhBehave | Download Source
Attention! In case you only need to attach events, you should use the Event Delegation method.