elementReady: a jQuery plugin

Tuesday, 14 August 2007

I have written a simple but useful jQuery plugin. elementReady calls a function during page load as soon as a specific element is available — even before the full DOM is loaded. It’s useful if you have unobtrusive JavaScript that you want to apply to particular page elements immediately, without having to wait for the whole DOM to load in a large page.

Using the elementReady plugin is very similar to the existing jQuery.ready() function, except that you must also pass in a string representing the ID of the element that should have the function call attached.

This version of the plugin works well for me in testing (and on this page), but it has not been exhaustively tested. I would appreciate your comments on the idea and the implementation.

Download

Get jquery.elementReady.js from the Github project.

Get the latest version from the elementReady jQuery plugin page. Get jquery.elementReady.js here. Then read on for instructions and details.

Documentation for jQuery.elementReady()

$.elementReady( String id, Function fn ) returns jQuery

While a page is loading, call a given callback function as soon as a specific
element is loaded into the DOM, even before the full DOM has been loaded.
Executes the function within the context of the element. This means that when
the passed-in function is executed, the ‘this’ keyword points to the specific
DOM element.

The function returns ‘this’, so you can chain multiple calls to
elementReady(). (Not that there’s much benefit in doing that.)

One argument is passed to the callback: a reference to the jQuery function.
You can name this argument $ and therefore use the $ alias even in
noConflict mode.

If the element has not been found by the time the DOM is fully loaded, then
the function will not be called.

Example:

Change the source of a specific image as soon as it is loaded into the
DOM (before the whole DOM is loaded).

$.elementReady('powerpic', function(){
    this.src = 'powered-by-jquery.png';
});

Example:

If you want to have the jQuery object instead of the regular DOM
element, use the $(this) function.

$.elementReady('header', function(){
    $(this).addClass('fancy');
});

Example:

Chain multiple calls to $.elementReady().

$.elementReady('first',  function(){ $(this).fancify(); })
 .elementReady('second', function(){ $(this).fancify(); });

Example:

Use the ‘$’ alias within your callback, even in noConflict mode.

jQuery.noConflict();
jQuery.elementReady('header', function($){
    $(this).addClass('fancy');
});

Example:

Change the polling interval to 100ms. This only works if $.elementReady() has not yet been called.

$.elementReady.interval_ms = 100;

Why?

I asked about this feature on the jQuery development list, and to my surprise nobody had made much of an effort to do this. jQuery author John Resig has some magic code that makes this happen automatically(!), but including this “auto” feature in the core would complicate things unnecessarily. He suggested that a plugin should be written. So here it is.

Implementation notes

The jQuery.elementReady() function is interesting because it’s quite unlike most other jQuery methods. It’s not a method applied to a normal jQuery object; it can’t be, because a jQuery object is normally constructed from a DOM element (or list of DOM elements). But in this case, all we have is the ID of an element: the element itself does not yet exist.

When the function is called, the matched element is passed to the callback function as a raw DOM element rather than a jQuery object. This is consistent with other event handlers, and is also the most efficient approach.

The function returns the global jQuery object, so you can chain calls to elementReady().

My implementation is inspired by the Yahoo UI library’s onAvailable(), but is simpler (and, to be sure, less well-tested). YUI also includes a related method called onContentReady(), which is supposed to fire when the element and all its content is available. I have not implemented that feature yet, but I think I may end up doing so in elementReady(). This will make it more robust yet still dead simple to use.

I thought about somehow overloading the original ready() function but thought that would just add complexity for no real benefit.

Things to do

This is an early version of the plugin. I’m afraid I have tested it only on Windows browsers, but I have had no problems with it yet. The main thing to do is to test it more thoroughly. As I mentioned above, I also might decide to modify the function to check that the entire contents of the target element are loaded, not just the element itself.

Have your say

Apparently blog posts get x% more comments if the last heading says “Have your say”. So have your say! Is this useful? How can it be improved?

Tags: , ,

Share this page:
  • Twitter
  • Digg
  • Slashdot
  • del.icio.us
  • Google Bookmarks
  • DZone
  • LinkedIn
  • Reddit
  • Facebook
  • Print

214 comments

You can leave a comment, or trackback from your own site.

  1. It fail with content loaded by usin ajax calls. This can be really useful to test if content loaded by ajax calls is ready or not but for some reason it doesn’t work.
    Any suggestion?

  2. @augustus
    use Oncomplete or onsuccess

  3. @tenshi, the component project does look interesting. But it needs a lot more documentation. I don’t really understand exactly what it does.

  4. I needed something like this also and came up with a jQuery plugin called “available”. Just throwing it out there in case anyone is interested….

    http://plugins.jquery.com/project/available

  5. @JT, that’s a very simple and elegant plugin. I worry about its robustness when used multiple times in large pages — performance may be an issue. But it looks as if it would work well when used carefully.

  6. @Bennett,

    Thanks for your input! I just posted a new version that should be much more efficient. It takes a similar single-timer approach this time, just had to work my brain around some context issues…

    It is surprising to me how little information exists about this problem. In my line of work I deal with lots of heavy pages where I can’t touch the source and I’ve never had a choice other than to do the polling. Take care…

    -JT

  7. I changed this a little so that we cound use it like this.
    $(‘#id’).elementReady(function(){
    $(this).show();
    });

    Here is the code:

    (function ($) {
    var interval = null;
    var checklist = [];
    $.fn.elementReady = function (fn, options) {
    // Create some defaults, extending them with any options that were provided
    var settings = $.extend({
    ‘interval_ms’: 23
    }, options);

    checklist.push({id: $(this).attr(‘id’), fn: fn});
    if (!interval) {
    interval = setInterval(function () {
    var docReady = $.isReady; // check doc ready first; thus ensure that check is made at least once _after_ doc is ready
    for (var i = checklist.length – 1; 0 <= i; –i) {
    var el = document.getElementById(checklist[i].id);
    if (el) {
    var fn = checklist[i].fn; // first remove from checklist, then call function
    checklist[i] = checklist[checklist.length - 1];
    checklist.pop();
    fn.apply(el, [$]);
    }
    }
    if (docReady) {
    clearInterval(interval);
    interval = null;
    }
    }, settings.interval_ms);
    }
    return this;
    };
    })(jQuery);

  8. @1st4ck, your version looks more polished than mine. Feel free to fork the Github project and send me a pull request. Otherwise I will probably incorporate your changes eventually anyway. Thanks!

  9. Emmanuel Mahuni

    i strongly suggest you incorporate the changes that 1st4ck suggested. I think that it is the way to go since that is jQuery syntax. Ok, there is something you need to understand, we don’t always want to give id’s to elements, thus why jQuery even uses css-like selectors as a way of finding things in the DOM. I don’t know, i haven’t checked yet even in the code, if 1st4ck’s method works with selectors such as classes etc, e.g: $(“.chinhu”).elementReady(…);.

    This could work pretty well, consider if i want to do something only when any specific class of elements have been loaded… or any input with specific attribs etc… I think you get the idea.

    And i think that’s the way to do it.

    A feature could also be added to only do something if a certain number of matching elements have been reached. You know what I love this plugin.
    People ague all the time about mootools vs jQuery, but face it mootools is dead coz of lack of easy access to DOM like jQuery, easy writing of code and scarce documentation. So if this plugin does things the jQuery way, then i think it will win more.

    But this is a great plugin.

  10. @Emmanuel, there may be a misunderstanding of what elementReady does. It is supposed to call a function when an element is ready; you can call it before the element even exists. So $(“.something”).elementReady(fn) doesn’t make sense, because if there are no elements with that classname then $(“.something”) will return an empty jQuery object,, and $(“.something”).elementReady(fn) won’t do anything.

    1st4ck’s code handles plugin options better and is more compact, so I might incorporate those changes, changing it to use jQuery instance syntax isn’t worthwhile.

    The reason elementReady only allows IDs is for performance; I will most likely change it to allow arbitrary selectors, but a poorly-chosen selector might cause performance problems on the page since it will be called more than 40 times a second. I might need to allow an option to keep checking even after an element exists, to allow for more elements coming into existence. Again though, this could lead to performance problems.

    In fact, I plan to rewrite the plugin to return a jQuery promise rather than the current ad hoc solution. This will make it much more consistent and flexible.

Leave a comment