Creating a JavaScript library for mobile: an introduction

Creating a JavaScript library for mobile: an introduction

An introductory article that tries to outline the development process of a JavaScript library for mobile devices.

A web developer should try to tailor his/her code to the specific requirements of a web project. Using a well-known JavaScript library is surely an excellent way to boost up your productivity. However, from a pure programming point of view you're actually neglecting an important aspect of the process: your programming skills and your knowledge of web standards will always remain on the same level if you never try to write something on your own. Mobile web development is an excellent field of study. Browsers such as Safari or Chrome have a superior level of support to web standards, so why don't we try to create a simple JavaScript library?

Using namespaces

A JavaScript library should live within a sandbox where its code can't clash with the namespace of other libraries. For that reason, we can use a self-executing function to create our sandbox:


(function() {
	var Lib = {};
})()

Now there's a problem: being included within a sandbox, our library is not accessible from outside of that function. If you try to access one of its members:


console.log(Lib.dom);

your browser will inform you that the Lib object is not defined. To make your library accessible, you have to bind it to the global window object:


(function() {
	var Lib = {};
	window.Lib = Lib;
})()

Now if you try again the same code from above:


console.log(Lib.dom);

you won't get undefined anymore.

The DOM

When you work on a mobile environment, you can actually get rid of all those hacks and workarounds that you were accustomed to on desktop platforms. IE6, IE7 and IE8 are no longer a problem. For that reason you can start using the DOM to its full potential.

Selecting elements is the first task you have to fulfill. On a desktop environment you surely have to write the following methods:


var Lib = {
	dom: {
			id: function(name) {
				var element = document.getElementById(name);
				return element;
			},
			tags: function(name) {
				var elements = document.getElementsByTagName(name);
				return elements;
			},
			classes: function(name) {
				var elements = document.getElementsByClassName(name);
				return elements;
			}
		}
};

getElementById(), getElementsByTagName() and getElementsByClassName() work as expected, but why use three different methods when you can take advantage of the more recent DOM Selectors API?


dom: {
	one: function(expr) {
		var element = document.querySelector(expr);
		return element;
	},
	all: function(expr) {
		var elements = document.querySelectorAll(expr);
		return elements;
	}
}

Now you can write:


var header = Lib.dom.one('#header');
var items = Lib.dom.all('#nav li');

Another crucial aspect of working with the DOM is to navigate through the hierarchy of nodes. jQuery provides us with a long series of DOM traversal methods that we can try to mimic on our library:


dom: {
			next: function(element) {
				var n = element.nextSibling;
				if (n !== null && n.nodeType == 1) {
					return n;
				} else {
					return this.next(n);
				}
			},
			nextAll: function(element) {
				var siblings = [];
				while (element.nextSibling) {
					element = element.nextSibling;
					if (element !== null && element.nodeType == 1) {
						siblings.push(element);
					}
				}
				return siblings;
			},
			prev: function(element) {
				var n = element.previousSibling;
				if (n !== null && n.nodeType == 1) {
					return n;
				} else {
					return this.prev(n);
				}
			},
			prevAll: function(element) {
				var siblings = [];
				while (element.previousSibling) {
					element = element.previousSibling;
					if (element !== null && element.nodeType == 1) {
						siblings.push(element);
					}
				}
				return siblings;
			},
			siblings: function(element) {
				var parent = element.parentNode,
					childs = parent.childNodes,
					sibls = [],
					len = childs.length,
					i;
				for (i = 0; i < len; i++) {
					var child = childs[i];
					if (child.nodeType == 1 && child !== element) {
						sibls.push(child);
					}
				}
				return sibls;
			},
			parent: function(element) {
				if (element.parentNode !== null) {
					return element.parentNode;
				} else {
					return null;
				}
			},
			children: function(element) {
				var childs = element.childNodes,
					len = childs.length,
					i, kids = [];
				for (i = 0; i < len; i++) {
					var child = childs[i];
					if (child.nodeType == 1) {
						kids.push(child);
					}
				}
				return kids;
			}
}

These are only some of the possible methods that we can implement by using the core DOM methods and properties, such as nextSibling, previousSibling, parentNode and childNodes. As you can see, we're only applying some of the most used DOM patterns already known by many web developers.

We can also filter out elements that match certain criteria:


dom: {
	filter: function(elements, test) {
				var result = [],
					len = elements.length;
				for (var i = 0; i < len; i++) {
					var current = elements[i];
					if (test(current)) {
						result.push(current);
					}
				}
				return result;
	}
}

For example:


var lis = Lib.dom.all('li');
var lisFilter = Lib.dom.filter(lis, function(current) {
	var text = current.firstChild.nodeValue;
	return /^\d+/.test(text);
});

In this case only the li elements whose text content starts with one or more digits will be included in the filtered set. We can also sort elements:


dom: {
	sort: function(elements, callback) {
				var arr = [],
					len = elements.length,
					i;
				for (i = 0; i < len; i++) {
					arr[i] = elements[i];
				}
				arr = arr.sort(callback);
				return arr;
	}
}

The above method first converts a DOM NodeList into an array and then applies the array's method sort() to the returned array. In this case we have only to remember that the comparison must be performed between two elements:


var lis = Lib.dom.all('li');
var lisSort = Lib.dom.sort(lis, function(a, b) {
	var aText = a.firstChild.nodeValue;
	var bText = b.firstChild.nodeValue;
	
	return (aText[0] > bText[0]);
});

We've simply compared the first letters of each element's text node to sort our set.

Events

Events must be handled according to the current W3C specifications, namely with the DOM Level 2 addEventListener() and removeEventListener() methods. So we need two simple utility methods that make use of the already mentioned DOM handlers:


events: {
	on: function(element, type, callback) {
		element.addEventListener(type, callback, false);
	},
	off: function(element, type, callback) {
		element.removeEventListener(type, callback, false);
	}
}

We also need an event that fires when the whole document has finished loading. The W3C provides the DOMContentLoaded event:


events: {
	ready: function(callback) {
		this.on(document, 'DOMContentLoaded', callback);
	}
}

We can also detect when an element is inserted or removed from the document tree by using the DOMNodeInserted and DOMNodeRemoved events:


events: {
	live: function(element, type, callback) {
		  this.on(document, 'DOMNodeInserted', function(event) {
					element = event.target;
					Lib.events.on(element, type, callback);
		   });
	},
	die: function(element, type, callback) {
		 this.on(document, 'DOMNodeRemoved', function(event) {
					element = event.target;
					Lib.events.on(element, type, callback);
		 });
	}
}

Mobile devices support orientation changes through the orientation property of the global window object. This property comes handy when we want to execute particular actions when the mobile device is in portrait or landscape mode or simply when it's been rotated by the user.

First, we can use the orientationchange event:


events: {
	orientation: function(callback) {
		if (window.orientation) {
			this.on(window, 'orientationchange', callback);
		}
	}
}

We can also determine whether we're in portrait or landscape mode by reading the numeric value of the orientation property:


events: {
	devicemode: function(params) {
		params.portrait = params.portrait || function() {};
		params.landscape = params.landscape || function() {};
		this.orientation(function() {
			var orientation = window.orientation;
				if (orientation == 0) {
					params.portrait();
				} else {
					params.landscape();
				}
	    });
	}
}

Conclusion

There are many other parts of our library that still need to be implemented, such as utility methods, AJAX methods, CSS methods and animation methods. I hope this introductory article has helped you to understand the process behind the implementation of a JavaScript library for mobile devices.