jQuery Document Ready…the sequel

My previous post jQuery Document Ready, the saga continues… got me thinking. Why not write a jQuery plugin which simulates a ready handler tied to an AJAX success handler. Let me explain.

Consider test1.html, view1.html and test1.js from the previous post:
test1.html

<html>
    <head>
        <title>Document Ready Demo</title>

        <script src = "jquery-1.4.2.js"></script>
        <script src = "test1.js"></script>

        <script>
            $(document).ready(function () {
                //make sure there is no caching to avoid confusion
                $.ajaxSetup({cache: false});

                $('#fetchView1').click(function () {
                    $.get('view1.html', function (data) {
                        $('#viewText1').html(data);
                    });
                });
            });
        </script>
    </head>

    <body>
        <button id = "fetchView1">Fetch View1</button>

        <div id = "viewText1">
            View1 content goes here
        </div>
    </body>
</html>

view1.html

<div>
   <input type="text" id="inputView1" value="Click button to fill with 1111" size="50" />
   <button id="buttonView1">Fill Input with number 1111</button>
</div>

test1.js

(function($) {
   $(document).ready(function(event) {

   console.log('test1.js document.ready executed');

   $('#buttonView1').click(function() {
        console.log('buttonView1 view clicked');
        $('#inputView1').val('1111');
   });
   console.log('buttonView1 button bound now');

//possibily more processing code and bindings

   });
})(jQuery);

Like I discussed, the intent of having the document.ready is that somehow it attaches itself to view1.html. Well it attaches itself to test1.html and because of this programmers resort to using live or liverquery. Again, nothing wrong with these tools but the penalty of freedom is performance overhead.

Ideally it would be nice if we could execute the JS code on AJAX success, rather automatically. Taking a cue and inspiration from the livequery plugin this is what I came up with:

jquery.jready.js

/*!
 * jQuery plugin jReady v1.0
 * https://phptouch.com/
 *
 * Copyright 2011, Jayesh Wadhwani
 * Dual licensed under the MIT or GPL Version 2 licenses.
 *
 * Released under the MIT, BSD, and GPL Licenses.
 *
 * Date: 31-July-2011
 */
(function($) {
    $.fn.jready = function(jreadyId, fn) {
        var j;
        //make a jready object
        j = new $.jready(this, jreadyId, fn);
        return this;
    };
    
    //jready function. Objects are made of this
    $.jready = function(context, jreadyId, fn) {
//        this.selector = selector;
        this.jreadyId = jreadyId;
        this.context = context || document;
        this.fn = fn;
        this.id = $.jready.fns.push(this) - 1;

        return this;
    };
    
    //function prototype
    $.jready.prototype = {
        //execute attached functions
        execute: function() { 
         var that = this; 
         this.context.each(function(){
            that.fn.apply(this);   
         })
         
        }
    };

    $.extend($.jready, {
        fns: [], //jready objects collection
        //jready handler
        execute: function(event, xhr, settings) {
            //check if there is a jready id present in the url
            var match = /.+?jready=(\w+)[\?\&]?/ig.exec(settings.url);

            if (match !== null) {
                var jreadyId = match[1] || null;
                //if there is a jready toke/id present then 
                //check if in the collection and execute the handler
                //in the object
                if (jreadyId) {
                    $.each($.jready.fns, function(index, fn) {
                        if (fn.jreadyId == jreadyId) {
                            fn.execute();
                            return false;
                        };
                    });
                }
            }
        },
        //bind the global ajax success
        bind: function() { $(document).ajaxSuccess($.jready.execute); }
    });
    // //bind the global ajax success when dom is ready
    $(function() { $.jready.bind(); });
})(jQuery);

I will explain the internals of the plugin in a minute.

Using this plugin test1.js now contains:

(function($) {
   $(document).jready('view1', function(event) {
      console.log('test1.js-view1 jready executed');    
      
      $('#buttonView1').click(function(){
         console.log('buttonView1 view clicked');
         $('#inputView1').val('1111');
      });
      console.log('buttonView1 button bound now');
      //event bidings
      
      //processing code
      
   });
})(jQuery);

Finally test1.html now contains:

<html>
    <head>
        <title>Document Ready Demo</title>

        <script src = "jquery-1.4.2.js"></script>
        <script src = "jquery.jready.js"></script>
        <script src = "test1.js"></script>

        <script>
            $(document).ready(function () {
                //make sure there is no caching to avoid confusion
                $.ajaxSetup({cache: false});

                $('#fetchView1').click(function () {
                    $.get('view1.html?jready=view1', function (data) {
                        $('#viewText1').html(data);
                    });
                });
            });
        </script>
    </head>

    <body>
        <button id = "fetchView1">Fetch View1</button>

        <div id = "viewText1">
            View1 content goes here
        </div>
    </body>
</html>

If I now run test1.html, no binding takes place. It only takes place when the AJAX call completes. Let me explain how that happens.

1. In the URL I have attached an identifying token, ‘view1.html?jready=view1. This token can be any alpha-numeric characters.

2. In test1.js I have replaced $(document).ready(function(event) with $(document).jready(‘view1’, function(event) . Note that the “event type” here is identical to the identifying token in the URL

3. When $(document).jready(‘view1’, function(event) is executed, all the plugin does is to store away the handler function in a function object which in turn is stored in an array.

4. Also note that the global handler ajaxSuccess at the end of the plugin code. The function attached to this event checks for a valid identifying token. If a valid token is detected then it iterates over all function objects looking for a match. If a match is found then it executes the jready handler.

I still have to work on the plugin, particularly in the area of error handling. But for now it should be enough to get you going.

Elementary I believe:-)

Happy Computing!

Advertisements

jQuery Document Ready, the saga continues…

Further to my earlier post jQuery Document Ready…use it wisely! one reader pointed out a different kind of architecture:

The html, test1.html:

<html>
    <head>
        <title>Document Ready Demo</title>

        <script src = "jquery-1.4.2.js"></script>
        <script src = "test1.js"></script>

        <script>
            $(document).ready(function () {
                //make sure there is no caching to avoid confusion
                $.ajaxSetup({cache: false});
                
                $('#fetchView1').click(function () {
                    $.get('view1.html', function (data) {
                        $('#viewText1').html(data);
                    });
                });
            });
        </script>
    </head>

    <body>
        <button id = "fetchView1">Fetch View1</button>

        <div id = "viewText1">
            View1 content goes here
        </div>
    </body>
</html>

When the button is clicked, an AJAX call is made. This call fetches the contents of view1.html and puts it in the div with id viewText1. Now let’s look at the view:

view1.html:

<div>
   <input type="text" id="inputView1" value="Click button to fill with 1111" size="50" />
   <button id="buttonView1">Fill Input with number 1111</button>
</div>


A very simple view file indeed. Just an input text field and a button. The objective being that when this button is clicked the event handler will fill the input field with '1111'. So where is the event binding and handler? That is in the included test1.js.

The included test1.js looks like this:

(function($) { 
   $(document).ready(function(event) {

   console.log('test1.js document.ready executed');

   $('#buttonView1').click(function() {
        console.log('buttonView1 view clicked');
        $('#inputView1').val('1111');
   });
   console.log('buttonView1 button bound now');

//possibily more processing code and bindings

   });
    
})(jQuery);

Note here that the button is attached to the click event handler which fills the input with '1111'. There is also a document.ready binding and its handler which will fire when the document is good and ready. The question is which document. The answer in a minute:-)

When I run test.html1 in a browser the console displays the following:

Console [1]= test1.js document.ready executed

Console [2]= buttonView1 button bound now

Now if I were to click on the button titled "Fetch View1" the AJAX call fires and the browser output should look something like this:

If you now click the button you probably expect to see the input box filled with '1111'. But when you do, nothing happens. However the console statement above does indicate that the button was bound!

The problem is that the binding in test1.js took place when the document.ready fired which was when test1.html was loaded. At this time view1 did not exist and obviously neither did the input field and the button. So the binding in test1.js was ineffective and of no use. Imagine 55 JS included files with an average of 5 binding in each and each file with a document.ready handler. Every handler firing when the outer document is loaded. Waste of resources!

The binding should actually take place after view1.html is loaded. Not fully understanding this concept programmers resort to using live event binding or the livequery plugin. Mind you, both these are superb tools but if used without proper understanding your application will suffer from severe performance issues.

Two simple solutions come to mind.

1. Put the JS code in view1.html wrapped in document ready.

<script>
 $(document).ready(function(event) {

   console.log('test1.js document.ready executed');

   $('#buttonView1').click(function() {
        console.log('buttonView1 view clicked');
        $('#inputView1').val('1111');
   });
   console.log('buttonView1 button bound now');


//possibily more processing code and bindings

   });
</script>
<div>
   <input type="text" id="inputView1" value="Click button to fill with 1111" size="50" />
   <button id="buttonView1">Fill Input with number 1111</button>
</div>

Remove the test1.js include from test1.html:

<html>
    <head>
        <title>Document Ready Demo</title>

        <script src = "jquery-1.4.2.js"></script>

        <script>
            $(document).ready(function () {
                //make sure there is no caching to avoid confusion
                $.ajaxSetup({cache: false});

                $('#fetchView1').click(function () {
                    $.get('view1.html', function (data) {
                        $('#viewText1').html(data);
                    });
                });
            });
        </script>
    </head>

    <body>
        <button id = "fetchView1">Fetch View1</button>

        <div id = "viewText1">
            View1 content goes here
        </div>
    </body>
</html>

If you run this, you will see that the bindings work fine and the input field is filled with '1111' when the button is clicked. Reason being that jQuery considers view1.html as a document and fires the document.ready event when the view1.html DOM has loaded. Remember this will only work if you are using jQuery methods such as html, append etc. It will not work if you use core dom methods/properties such as innerHTML.

2. Make test1.js a function call.

You could also change test1.js as a function and call it after the AJAX call completes like so:

test1.js will now contain:

function test1(){ 
   console.log('test1.js function executed');

   $('#buttonView1').click(function() {
        console.log('buttonView1 view clicked');
        $('#inputView1').val('1111');
   });
   console.log('buttonView1 button bound now');

//possibily more processing code and bindings
}

test1.html will now become:

<html>
    <head>
        <title>Document Ready Demo</title>

        <script src = "jquery-1.4.2.js"></script>
        <script src = "test1.js"></script>

        <script>
            $(document).ready(function () {
               $.ajaxSetup({cache: false});
               
                $('#fetchView1').click(function () {
                    $.get('view1.html', function (data) {
                        $('#viewText1').html(data);
                        test1();
                    });
                });
            });
        </script>
    </head>

    <body>
        <button id = "fetchView1">Fetch View1</button>

        <div id = "viewText1">
            View1 content goes here
        </div>
    </body>
</html>

and finally view1.html will go back to its original form:

<div>
   <input type="text" id="inputView1" value="Click button to fill with 1111" size="50" />
   <button id="buttonView1">Fill Input with number 1111</button>
</div>

If you now run test1.html it will work as expected. Note the function call test1.js() after the AJAX call completes.

This is only the beginning. I am sure you have thought of other ways of doing this. My main purpose however of writing this post is to emphasize not to use document.ready, live bindings or livequery before fully understanding as to what is involved. For everything there is a purpose and a matching tool. Be prudent and pragmatic.

Happy programming!

jQuery Document Ready…use it wisely!

jQuery has done an excellent job with the document .ready() function which allows us to accurately know when the DOM has been loaded so that we can begin processing our JavaScript code. Prior to this we had to rely on the windows onLoad event which tends to occur much later in the load cycle.

Usually, one ready handler per page or view is used as the document ready handler will parse/execute the code once the DOM has loaded. Having this handler process code not relevant to the document at hand would be wasting resources.

Unfortunately I have noticed that this facility is used freely and loosely, even when sometimes it is not required, under the guise of “just to be on the safe side”. Not using this function properly has an impact on the performance of the application.

Consider a very simple example. We have two JS files test1.js and test2.js which are respectively included in test1.php and test2.php. These files are shown below:

test1.php

<html>
<head>
   <title>Test 1</title>
   
   <script src="jquery-1.4.2.js"></script>   
   <script src="test1.js"></script>
</head>
<body>

<div id="testDiv1">
<?php
$rows = 100;
echo    '<table>';
for($i = 0; $i < $rows; $i++){
	echo "<tr>";
	echo " <td>{$i}</td>";
	echo "</tr>";
}
echo '</table>';
?>
</div>
</body>
</html>

and the JS include file test1.js:

(function($) {
   $(document).ready(function(event) {
      console.log('test1.js - Number of tr elements: ' + $('#testDiv1 table tr').length);    
      
      //assume event bindings
      
      //assume processing code
      
   });
})(jQuery);

When run, the php file prints out a table structure with 100 rows. When the DOM is loaded(the table structure in our case) the ready event fires and prints out a message on the console:
test1.js – Number of tr elements: 100

test2.php and test2.js are similar, except this one prints out the number of ‘td’ elements instead of tr.

test2.php

<html>
<head>
    <title>Test 2</title>
   
   <script src="jquery-1.4.2.js"></script>   
   <script src="test2.js"></script>
</head>
<body>

<div id="testDiv2">
<?php
$rows = 100;
echo    '<table>';
for($i = 0; $i < $rows; $i++){
	echo "<tr>";
	echo " <td>{$i}</td>";
	echo "</tr>";
}
echo '</table>';
?>
</div>
</body>
</html>

and the JS include file test2.js:

(function($) {
   $(document).ready(function(event) {
      console.log('test2.js - Number of td elements: ' + $('#testDiv2 table td').length);    
      
      //assume event bindings
      
      //assume processing code
      
   });
})(jQuery);

When run the console prints out:
test2.js – Number of td elements: 100

As long as each JS file with the ready function is included in its respective host file or view everything works as it should without any overlap or conflict. But life is not so simple.

To reduce page load times it is customary to join many files into one and then minify the resulting file. This takes away the performance degradation due to latency issues as only one file is loaded compared to many.

By the same token it is also advisable that JS code specific to a view or a page be loaded per page while common code be bundled together and minified. This leads to a highly efficient load which does not tax the resources and also keeps the latency issues to a minimum.

Recently I came across an open source project based on the MVC pattern which joined 55 JS files, with each file having its document ready function. This bundle included common and specific files. Let me illustrate to you how much of a performance impact this can make. Let’s join test1.js and test2.js into one file and call it test.js. I have not minified this file for the sake of clarity.

test.js

(function($) {
   $(document).ready(function(event) {
      console.log('test1.js - Number of tr elements: ' + $('#testDiv1 table tr').length);    
      
      //assume event bindings
      
      //assume processing code
      
   });
})(jQuery);

(function($) {
   $(document).ready(function(event) {
      console.log('test2.js - Number of td elements: ' + $('#testDiv2 table td').length);    
      
      //assume event bindings
      
      //assume processing code
      
   });
})(jQuery);

Also let’s modify test1.php to include this file:
test1.php

<html>
<head>
   <title>Test with joined JS file</title>
   
   <script src="jquery-1.4.2.js"></script>   
   <script src="test.js"></script>
</head>
<body>

<div id="testDiv1">
<?php
$rows = 100;
echo    '<table>';
for($i = 0; $i < $rows; $i++){
	echo "<tr>";
	echo " <td>{$i}</td>";
	echo "</tr>";
}
echo '</table>';
?>
</div>
</body>
</html>

If we now run this we get the following on the console:
Console [5]=
test1.js – Number of tr elements: 100

Console [6]=
test2.js – Number of td elements: 0

Notice that there are two document ready handlers in test.js, and both ran. This is because when the DOM loads they cannot distinguish a relevant file from an irrelevant one. The first message is valid as it relates to test1.php but the second is not, as it relates to test2.php. This means the effort put in by the JS engine for the second handler was a waste.

Usually the document ready handler does not contain a simple log message, but a slew of event bindings and other JS code. Some of the files in the aforementioned project had close to a dozen event bindings. Even if we were to assume an average of five event bindings per file, that’s 275 bindings. Take into account event bubbling, live bindings and we are talking serious performance problems.

In such circumstances we cannot rescue everything but we can inject a little saving grace.

Here is the same test1.php and test.js with changes, which I will explain in a minute:

test1.php

<html>
<head>
   <title>Test with joined JS file</title>
   
   <script src="jquery-1.4.2.js"></script>   
   <script src="test.js"></script>
</head>
<!-- Note the new title attribute -->
<body title="test1">

<div id="testDiv1">
<?php
$rows = 100;
echo    '<table>';
for($i = 0; $i < $rows; $i++){
	echo "<tr>";
	echo " <td>{$i}</td>";
	echo "</tr>";
}
echo '</table>';
?>
</div>
</body>
</html>

Notice that in the body tag I have added a title attribute.

test.js

(function($) {
   $(document).ready(function(event) {
      //check if for this handler ***NEW****
      var title = $('body').attr('title');
      if(title !== 'test1'){
         return false;
      };
      console.log('test1.js - Number of tr elements: ' + $('#testDiv1 table tr').length);    
      
      //assume event bindings
      
      //assume processing code
      
   });
})(jQuery);

(function($) {
   $(document).ready(function(event) {
      //check if for this handler ***NEW***
      var title = $('body').attr('title');
      if(title !== 'test2'){
         return false;
      };
      console.log('test2.js - Number of td elements: ' + $('#testDiv2 table td').length);    
      
      //assume event bindings
      
      //assume processing code
      
   });
})(jQuery);

If you run this now you will get the following in the console:
test1.js – Number of tr elements: 100

Let me explain why.

In test.php I have added a title attribute which identifies this file

In the JS file I have added the following code in the document ready handler:
//check if for this handler
var title = $(‘body’).attr(‘title’);
if(title !== ‘test1’){
return false;
};

It only continues to processes the handler code if the title attribute matches “test1”. So the handler connected with test2 never continues which saves resources.

The crux of the idea is to make processing relevant to the document at hand. This is only one way of doing it. Many other ways come to mind and I leave them to your imagination.

I hope this helps you and I look forward to your feedback.