> Do you really need jQuery (for mobile dev)?

frame iphone

I love jQuery, I use it everyday, you can spot it even on this blog. It’s a life saver in the times of desktop browser discrepancies (yes, I’m talking to you IE), but modern browsers and specifically mobile browsers are good enough not to need any bloated framework on their shoulders. In this post I’m showing you that 90% of the times you are using a framework for nothing.

Why should I care?

Very good question indeed. It’s not a matter of über-optimization or the blabbing of a javascript fanatic. Customized code is snappier, faster to load and makes you feel better (and we are not talking about 0.5% faster).

Mobile devices are hogs. Even the fastest one knees before a slow connection. That’s why your code needs to be optimized, optimization shouldn’t be a plus, it should be part of your mobile project, and a very important part of it.

The problem is that the more you use a framework the more you get used to it and the lazier you get.

CSS Selector

Most of the times you’d use jQuery for its $elector. It’s just pure magic, but if you are targeting iPhone or Android or BlackBerry or any recent mobile browser, you get that magic for free. You don’t really need 100kb of a framework.

document.querySelectorAll();

querySelectorAll is the easiest, fastest, more reliable, native CSS selector out there.

function $(query) {
	return document.querySelectorAll(query);
}

Here you have your beloved $. Three-lines-of-code. You may argue that getElementById is probably faster if you need to get elements by their ID, but do not confuse optimization with over-optimization. How many elements could a mobile page have? Plus, you should really cache the elements you are going to use in your code, so you are not probably querying DOM 30 times per second.

Ready function

Of course we cannot use our newly created selector until the DOM is ready. jQuery has the ready() function, and so do we.

function ready (fn) {
	document.addEventListener('DOMContentLoaded', fn, false);
}

All functions given to ready() will be fired when DOM is loaded (not images, just the DOM). Here’s an example:

ready(function () {
	alert($('#id')[0].innerHTML);
});

Of course this is not very flexible. All ready functions are called anonymously so we cannot unload them once executed. But if you are calling it only once, this is all you need.

I cannot foresee all possible scenarios where the code is executed, that’s why frameworks are bloated: they tend to satisfy the largest audience possible, but you don’t want IE code on your iPhone, right?

To make our ready function classy, we need to get rid of the anonymous function. Here’s how we could do in plain javascript:

var readyFunc = [];

function DOMReady () {
	for(var i=0, l=readyFunc.length; i<l; i++) {
		readyFunc[i]();
	}

	readyFunc = null;
	document.removeEventListener('DOMContentLoaded', DOMReady, false);
}

function ready (fn) {
	if (readyFunc.length == 0) {
		document.addEventListener('DOMContentLoaded', DOMReady, false);
	}

	readyFunc.push(fn);
}

Now you can call ready() multiple times inside your document, the functions will be called one by one starting from the first to the last one declared. At the end of the process all functions will be unloaded, freeing precious resources.

We now have a selector and we can run it when document is ready. We covered 50% of the jQuery daily usage.

NodeList and prototyping

From the code above you probably noticed that I had to specify an index ([0]) to get the innerHTML of the element, even though I’m querying an ID (and presumably there’s only one of them).

That’s because querySelectorAll returns a NodeList, not the node itself. If you need to get just one element you can use querySelector which returns the plain queried element.

Working with NodeLists can be annoying. If you want to apply a new style to all fetched elements, you have to loop through the whole array.

To help us in this task we could create a function that loops the array for us. For example:

var elements = $('li');
forEach(elements, doSomething);

This would work but, admittedly, is lame. We could better add a new method to the NodeList so that all queried elements will have the forEach feature inside them.

$('li').forEach(doSomething);

Nicer. This is achieved by creating a custom NodeList object to which we attach all the methods we need.

We have to reorganize the code we did so far first. We are going a bit techy here, stay we me.

// Create our own namespace (because it's cool)
(function () {

// Define the core element
var	$ = function (query) {
		return new customNL(query);
	},
	
	// Custom Node List
	customNL = function (query) {
		query = document.querySelectorAll(query);

		this.length = query.length;
		for (var i=0; i<this.length; i++) {
			this[i] = query[i];
		}
		
		return this;
	},
	
	// Holds all functions to be executed on DOM ready
	readyFn = [],
	
	// Executed on DOMContentLoaded
	DOMReady = function () {
		for(var i=0, l=readyFn.length; i<l; i++) {
			readyFn[i]();
		}

		// free some mem
		readyFn = null;
		document.removeEventListener('DOMContentLoaded', DOMReady, false);
	};
	
// Used to execute functions on DOM ready
$.ready = function (fn) {
	if (readyFn.length == 0) {
		document.addEventListener('DOMContentLoaded', DOMReady, false);
	}

	readyFn.push(fn);
};

// Expose $ to the world
window.$ = $;

})();	// Execute our namespace

Now we’re ready for prototyping.

The whole code is inside a (function () { ... })() so it doesn’t collide with other libraries you’d want to include.

We create a customNL every time $() is called. Queried elements are stored directly inside the customNL object, so you access them as they were native NodeLists (eg: $(query)[n]). We also added a length parameter which returns the number of elements in the array ($(query).length). At this point we just replicated the native NodeList, let’s add some prototypes (= more functionalities) to it.

customNL.prototype = {
	// Execute code for each element in the Node List.
	each: function (callback) {
		for (var i=0; i<this.length; i++) {
			callback.call(this[i]);
		}
		
		return this;
	},

	// Set/Get styles to the elements
	style: function (attrib, value) {
		if (typeof attrib == 'string' && value === undefined) {
			return window.getComputedStyle(this[0], null).getPropertyValue(attrib);
		}
		
		if (typeof attrib != 'object') {
			attrib[attrib] = value;
		}

		return this.each(function () {
			for (var i in attrib) {
				this.style[i] = attrib[i];
			}
		});
	},
}

We introduced the phantomatic each function that loops through the nodes and apply whatever action to all of them. We also added the style method that sets or gets any style taking advantage of each.

$.ready(function () {
	$('li').style({ color: 'red'});		// Here we set the color to 'red' to all LIs in the document
	console.log($('li').style('color'));	// This returns the style.color of the first element in the row
})

Please note that the ready function is now part of the $ object.

To further extend our customNL we have to make it a bit smarter.

	customNL = function (query) {
		if (query.nodeType) {			// query is already a Node
			query = [query];
		} else if (typeof query == 'string') {	// query is a string
			query = document.querySelectorAll(query);
		} else if (!(query instanceof Array)) {	// if none of the above, query must be an array
			return null;
		}

		this.length = query.length;
		for (var i=0; i<this.length; i++) {
			this[i] = query[i];
		}

		return this;
	},

Now we can pass a node to our function that will inherit all custom node list functionality. Next we add the item method that returns one element of the node list. Inside the prototype we add the following:

	item: function (num) {
		return $(this[num]);
	},

Usage:

$('li').item(2).style({color:'red'});	// This lights the third LI

We finally have a few kilobytes very basic, tiny-weeny framework we can work on.

Event Handling

No web app lives without events. Stay focused, we are going to add event listeners.

	bind: function (type, fn, capture) {
		return this.each(function () {
			this.addEventListener(type, fn, capture ? true : false);
		});
	},
	
	unbind: function (type, fn, capture) {
		return this.each(function () {
			this.removeEventListener(type, fn, capture ? true : false);
		});
	}

No voodoo involved, no fairy dust nor unicorn pee. To add a click event to elements do the following:

$('li').bind('click', function () { console.log('clicked') });

capture is optional, defaulted to false. You can be more specific if you want, you may want to create a click shortcut, or you can even create custom events such as tap or swipe, but we will talk about that next time, if this post will get any kind of response.

Parent

Sometimes we may need to get the parent of an element. Add the following to the prototype.

	parent: function () {
		var result = [], parent, i, l;
		this.each(function () {
			parent = this.parentNode;
			if (!parent._counted) {
				result[result.length] = parent;
				parent._counted = true;
			}
		});
	
		return $(result).each(function () {
			delete this._counted;
		});
	}

This code returns all parents of the selected elements taking care of removing duplicates. Take a look at the following code:

<ul>
	<li>One</li>
	<li>Two</li>
	<li>Three</li>
</ul>

<script>
var parent = $('li').parent();
</script>

parent variable will have just one UL node and not three ULs all equal. On the other hand the following…

<ul>
	<li><span>One</span></li>
	<li><span>Two</span></li>
	<li><span>Three</span></li>
</ul>

<script>
var parent = $('span').parent();
</script>

… will return three LIs.

Extending objects functionality

Sometimes you need to add features to an already created object. Extending can be useful to build plugins for example, or to add core features to the $ object (such as the ready function). Take a look at the following code:

$.extend = function (obj, target) {
	target = target || customNL.prototype;	// To help plugin development
	for (var prop in obj) {
		target[prop] = obj[prop];
	}
};

All it does is merging two objects. If target is not defined the customNL class is extended by default (to help plugin development, maybe I’ll talk about this next time). With the extend function we can now easily add features to the $. Let’s start from the ready function and add some more.

$.extend({
	isIpad: (/ipad/gi).test(navigator.appVersion),
	isIphone: (/iphone/gi).test(navigator.appVersion),
	isAndroid: (/android/gi).test(navigator.appVersion),
	isOrientationAware: ('onorientationchange' in window),
	isHashChangeAware: ('onhashchange' in window),
	isStandalone: window.navigator.standalone,
	has3d: ('WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix()),

	// Execute functions on DOM ready
	ready: function (fn) {
		if (readyFn.length == 0) {
			document.addEventListener('DOMContentLoaded', DOMReady, false);
		}

		readyFn.push(fn);
	}
}, $);

You have now access to $.isIpad or $.isStandalone. You may as well have done like this:

$.isIpad = (/ipad/gi).test(navigator.appVersion);
$.isIphone = (/iphone/gi).test(navigator.appVersion);
$.isAndroid = (/android/gi).test(navigator.appVersion);
...

But the extend way is shorter and easier to read.

Expanding the micro-framework

At this point you should be able to expand the micro-framework to your needs. Have a look at the version I set up for you which includes some more useful functions such as: html(), width/height(), hasClass(), addClass(), removeClass(). Don’t use it in production, it’s just a proof of concept.

Conclusion

Please, don’t make this a fanboy war. I mentioned jQuery just because it’s wide spread, but I could have picked any framework. The point is not that frameworks are bad or useless, it’s that sometimes you probably don’t need them. Also getting your hand dirty in the code helps you understand how javascript actually works helping you choose better coding paths even when using frameworks.

On your daily usage you’d probably need even less or by the way different features than the ones covered by my lame micro-framework. This post is just to keep your mind alert and to wake you up from the frameworks laziness.

/Share the joy

/Reactions

  • NIce work, I was literally just asking myself the same question as I’m starting on a HTML5 prototype project for use on the iPad.

    Question: presumably it would be simple to extend this to have some basic ‘bespoke’ event listeners set up in the framework, such as touchstart, touchmove, touchend etc.

    Would you think its best to include this in the micro framework or just using your event bindings to set up custom listeners, or maybe a 3rd option I haven’t thought of..?

    • I’d use the simple bind() for click, focus, or even touchstart if you don’t need added functionality. If you need a “tap” (touchstart-move-end sequence) I’d make a custom code for it.

      Tap and Swipe are to very common events on mobile, it’s worth making a custom event just for them. You could even make them as plugin and link only if needed.

      The “tap” event could also assign a “active” class to the target object.

  • You and anyone interested in this should definitely check out XUI from Brian LeRoux and friends; it does almost exactly what you outline above, and each piece is built as a module so you can create a build of it that only includes exactly the pieces you need. It’s tiny, still has the jQuery “feel,” and has been around the block a bit and has tests to back it up. http://xuijs.com/

  • Thanks, I’ve built on that now and have adapted the Ryan’s jQuery swipe plugin (http://www.ryanscherf.net/demos/swipe/jquery.jswipe.js)

    I have another question though. What about emitting custom events, i.e. $.trigger()? I think that would a really useful thing to have in a library like this.

    • well, yes, the trigger() thing can be done, probably with dispatchEvent. Personally I don’t like to use trigger() that much.

  • Windows Mobile Phone 7 is coming, and is going to blow the Golden Age of mobile web browsers right out of the water.

    You may have heard that the web browser for the MoPho-7, as I like to call it, is based on IE7. It’ll leave you crying for a framework, I’m sure.

    • … as far as it is optimized for mobile devices :)

      • Author: H4CK3R
      • Posted on: 2010/08/27
      • At: 10:16

      I don’t believe that win 7 mobile will be less of a flop then win 6, which jquery’s mobile section has listed as “avoid” developing for under it’s browser comparison.

    • You can make it fancy and small, but its still Internet Explorer. For every hour one spends making a web app, they have to spend another making it work for IE. I do love the Windows Phone 7 concept though, but its weak point is definitely the browser.

    • Author: Dan Santner
    • Posted on: 2010/08/27
    • At: 00:34

    Glad you wrote this Matteo. I was wondering the same thing after leaving the apple conference and seeing some amazing webapp presentations with no frameworks used to code them.

    And like you say, it’s not that jquery, ext, dojo, etc are bad animals. It’s just that they are becoming less important as mobile converges on webkit.

    I do think windows mobile is the dark horse coming however. But until they prove they can market a phone, I’m not shaking in my boots.

    • Author: Roberto
    • Posted on: 2010/08/28
    • At: 17:22

    Cool ? What about a microfw iScroll ? To avoid code duplication, when using both (and I have a concrete project in mind). Or iScroll could just use by default microfw as dependency, to guarantee continuous maturity of both.

    • I’d like to keep iScroll as standalone as possible. I agree that something more can be done to avoid code duplication, though.

    • Author: Roberto
    • Posted on: 2010/08/28
    • At: 17:27

    Really great ! I just finished looking at the code. Have already ideas for plugins. This is the way to write highly optimized mobile apps !

    • Author: mystix
    • Posted on: 2010/09/07
    • At: 19:47

    very neat!

    saw this in your bind() method though (and something similar in unbind()):

    this.addEventListener(type, fn, capture ? true : false);

    which ought to be shortened to just:

    this.addEventListener(type, fn, !!capture);

    • smart :)

      • Author: Aru
      • Posted on: 2010/09/20
      • At: 13:39

      or could be just simple like this :
      this.addEventListener(type, fn, capture);

    • Author: dmitry
    • Posted on: 2010/09/24
    • At: 15:10

    Cewl!

    Already use it, will share with result a bit later when (if) all gone

    Thank you very much!!!

  • hi do you like car rims?

  • Is there a place where something like this is being distributed? You built something quite nice but I was hoping for an actual release of a micro fw based on jquery. If you have any pointers that would be much appreciated! Just checking before I go hacking away!

    • well, this is just a proof of concept. Not ready for production I guess. Anyway, in the coming days I’m releasing a new project that will be based on a revisited version of this framework. I’ll find the project on github.

    • Author: Wade Harrell
    • Posted on: 2010/12/03
    • At: 18:40

    http://www.jslint.com/

    jslint caught a couple simple things; some of its warnings i feel are safe to ignore; but ===, extra commas, and missing semicolons are good catches.

    • Author: adam.lu
    • Posted on: 2010/12/30
    • At: 06:12

    oh! micro jquery : )

    • Author: richbyte
    • Posted on: 2011/02/13
    • At: 17:16

    Very good article. Have you heard about zepto.js?
    http://zeptojs.com/

    • indeed, very good work of a very talented developer!

    • Author: zhuqi
    • Posted on: 2011/02/19
    • At: 16:06

    thanks

    • Author: Rudiger
    • Posted on: 2011/03/16
    • At: 19:44

    Does iScroll work with Zepto.JS?

    For those that don’t know, Zepto.JS is a “minimalist JavaScript framework for mobile WebKit browsers, with a jQuery-compatible syntax.”

    Sounds like the perfect thing for iScroll!

    http://zeptojs.com

      • Author: Rudiger
      • Posted on: 2011/03/16
      • At: 19:50

      Nevermind; seems like iScroll is standalone…

      Great stuff, by the way!

  • http://zeptojs.com/ is basically this. jQuery without the cruft.

  • https://github.com/wharrell1271/JSLint-compliant-version-of-lame-micro-framework

    hope you don’t mind, I’ll take it down if you prefer.

    • the lame micro fw is just a proof of concept, a lot of work should be done with it. anyway I’m glad you like and work on it.

  • Funny thing is. You start to make your own framework and a few projects later, do you have something bigger and probably slow than jQuery.

  • Hey Matteo, i’m real newbie and am trying to get the onclick method working on iPhone. Apparently I have to use the Tap method? Have you any idea how I would go aboput coding this? The site I’m testing on is bingbangbosh.ie and below is the segment of code i’m trying to get to work. Many Thanks! Frank

    • Author: Haluk Karamete
    • Posted on: 2011/09/28
    • At: 18:47

    Isn’t the right way to include jQuery to tap into the Google repository? And if that’s so, isn’t JQuery is already on the client’s cache? So why worry about it?

    • Author: Erik Roznbeker
    • Posted on: 2011/11/04
    • At: 08:33

    I was just researching how to create small and fast jquery/jquery mobile alternative for our web app and this is great resource!
    Thanks!

    • Author: Josh
    • Posted on: 2012/01/24
    • At: 22:02

    Its only in the client’s cache if they’ve been to a site with that specific file version of jQuery. But that isn’t really the point. Just having the framework on the client isn’t where the performance issues occur. Its initiating calls to that framework that in many cases can more efficiently be addressed using javascript. For instance, using (‘#element’) then uses the framework to call document.getElementById(‘element’) — which is woefully inefficient – especially on a mobile platform.

    • Author: Jean
    • Posted on: 2012/02/28
    • At: 18:39

    By doing this you’re throwing backwards compatibility out the window. Most phones are *not* androids or iphones.

  • I just couldn’t help but comment. I so totally agree with using pure HTML5/Javascript and libraries like iscroll to get much better performing mobile apps. I’m currently developing a simple, single page (multiple views) app that feels native (smooth transitions and all)

    • Author: c.
    • Posted on: 2012/07/03
    • At: 15:11

    You may argue that getElementById is probably faster if you need to get elements by their ID, but do not confuse optimization with over-optimization. How many elements could a mobile page have? Plus, you should really cache the elements you are going to use in your code, so you are not probably querying DOM 30 times per second

    Why are you implying that you need a framework to cache your DOM query? Because you have a hardon for using $? You get caching for free, it’s called “variables”

    Isn’t the right way to include jQuery to tap into the Google repository?

    It doesn’t matter. The newest stats (2010) I can find on browsers for mobile devices have a cache limit of 4MB. JQuery by itself is 24KB (minified+gzipped). The odds of keeping this in your cache is pretty small when you consider all of the other assets you’ll be downloading on any given page (CSS, images, other JS files, the page itself).

    • You get caching for free, it’s called “variables”

      exactly!

    • Author: BuddyL
    • Posted on: 2012/08/14
    • At: 19:13

    Wow, the comments are as good as the article! Thanks Matteo and all else. This was an extremely helpful page.

    I come from a little different angle though – I have spent too many hours trying to figure out whether problems I am incurring are due to jQuery bugs or my own misunderstanding. I’m more of a server-side programmer anyway and this stuff drives me nuts when I can’t even write sample code to figure out what is happening.

    Straight coding does not take more time when you are spending half your time with a framework trying to figure out why it is doing what it is doing.