Home > javascript, jquery > Markup-based unobtrusive comprehensive DOM-ready execution

Markup-based unobtrusive comprehensive DOM-ready execution

March 11th, 2009

On a recent project I took my previous approach to automating firing of onload events to a new level.

For instance if your code was architected in an object literal such as:

 
FOO = {
  common : {
    init     : function(){ ... },
    finalize : function(){ ... }
  },
  shopping : {
    init     : function(){ ... },
    cart     : function(){ ... },
    category : function(){ ... }
  }
}

A page with this body tag:

<body id="cart" class="shopping">

would load these functions sequentially:

UTIL.fire is calling: FOO.common.init()
UTIL.fire is calling: FOO.shopping.init()
UTIL.fire is calling: FOO.shopping.cart()
UTIL.fire is calling: FOO.common.finalize()

In addition, using these classes and IDs on the body tag provides some excellent specific hooks for your CSS.

The javascript:

UTIL = {
 
  fire : function(func,funcname, args){
 
    var namespace = FOO;  // indicate your obj literal namespace here
 
    funcname = (funcname === undefined) ? 'init' : funcname;
    if (func !== '' && namespace[func] && typeof namespace[func][funcname] == 'function'){
      namespace[func][funcname](args);
    } 
 
  }, 
 
  loadEvents : function(){
 
    var bodyId = document.body.id;
 
    // hit up common first.
    UTIL.fire('common');
 
    // do all the classes too.
    $.each(document.body.className.split(/\s+/),function(i,classnm){
      UTIL.fire(classnm);
      UTIL.fire(classnm,bodyId);
    });
 
    UTIL.fire('common','finalize');
 
  } 
 
}; 
 
// kick it all off here 
$(document).ready(UTIL.loadEvents);

This system worked very well and keeps you in serious control of the execution order.

In the end, I used this plus a custom event to bind super low priority script.
For example:

$(document).bind('finalized',function(){ ... }); // placed within a FOO.shopping.category()

And I'd trigger that

$(document).trigger('finalized');

at the very end of UTIL.loadEvents(). This allows you to keep similar code together, but delay portions responsibly without any setTimeout ugliness.

2010.10.28: OMG this shit just got next level….

So Blake Waters presented this topic in August this year.

And now Jason has published his approach on the most excellent Viget Inspire blog. Please check it out.

It's essentially using data-attributes instead of classes to trigger this action.. So..

   <body data-controller="users" data-action="show">

Will fire off:

FOO.common.init();
FOO.users.init();
FOO.users.show();

Boom boom bam. I dig it. Check it out in its full glory.

Paul Irish javascript, jquery

  1. redsquare
    March 23rd, 2009 at 06:19 #1

    Nice Paul, Just starting a greenfield app and have used a variation of this, rather than using body id and class I have used the rest url peices
    e.g appName/Controller/Action – this provides me with common (appName), then more specific implementations using controller and finally the action. Seems to be working good although only a few hours into it. I hope this will spare the developers having to remember to specify body id and class. Any thoughts / issues with this?

  2. March 23rd, 2009 at 11:21 #2

    @redsquare,
    Ah that's pretty nice. I would only go that route if you're confident the URL structure doesn't need to change and you don't have any SEO needs out of the site.

  3. February 11th, 2010 at 16:33 #3

    This is an excellent solution. Just tested out on one current project and the application feels much smoother

  4. August 10th, 2010 at 21:24 #4

    Great idea! I can't believe I haven't thought of this previously. :)

  5. August 11th, 2010 at 13:01 #5

    I believe I got here from html5boilerplate.com so sorry if I'm more than a year late.

    Well, I like this but you can do the same without the whole UTIL object if you're already using jQuery and if performance aren't really a problem.
    I'm using this at the moment:

    $(function() {
    	//common.init
    	if($("#gallery").length)
    	{
    		//gallery page
    	}
    	else if($("#contacts").length)
    	{
    		//contacts page
    	}
     
    	if($(".classname").length)
    	{
    		// do all the *needed* classes too.
    	}
    	//common.finalize
    });
  6. August 11th, 2010 at 13:28 #6

    @Bfred
    The classes part is where your code breaks down.. Not to mention this anti-DRY code is gonna get really hairy as this file grows..
    But sure.. it's possible to do it this way.. sorta. kinda. :/

  7. August 12th, 2010 at 15:23 #7

    Hi, a simple question Paul
    Should be any difference between this:

    funcname = (funcname === undefined) ? 'init' : funcname;

    and this way I often see:

    funcname = funcname || 'init';

    Thanks

  8. August 12th, 2010 at 15:31 #8

    @David
    The only different is the latter will go to 'init' if funcname is also false, 0, and most importantly, '' (empty string).
    Your call if that's okay or not.

  9. A Lisman
    September 12th, 2010 at 13:32 #9

    I don't understand what you gain by triggering the modules (e.g. shopping) with the body tag class. Isn't that conflating style and functionality? I mean, you might want a global shopping class, but then again you might not. Why not just stay in javascript and register the modules explicitly?

  10. September 12th, 2010 at 14:18 #10

    Isn't that conflating style and functionality?

    @A Lisman, no, not at all. Markup describes the content of the page. Clearly the content of the page very much has to do with shopping and the cart. Those classes may be used for styling (that would be wise, certainly), but don't need to.

    Not exactly sure how you're proposing to "register the modules explicitly" but I'm not thinking of a possible implementation where that would be either performant or violate separation of behavior/content much worse.

    The advantage here is that we have complete control over what part of our app runs based on the current page.
    Kind of like Sammy.js's route-specific callbacks.

  11. September 13th, 2010 at 01:37 #11

    How do you handle partial updates, via XHR, in your app though? This application model only covers site-loading.

    And just 2c from the JS smart-ass ;)

    funcname = (funcname === undefined) ? 'init' : funcname;

    would be better and easier with:

    funcname = (funcname == null) ? 'init' : funcname;

    With type-converting equality comparison (===) Null and Undefined are equal, what your code probably wants to check. Even cleaner would be to check for a String-type.

    p.s. and why would you reassign funcname with itself for most cases? Using if seems longer than the conditional operator but will result in the same amount of (better readable) code.

  12. November 13th, 2010 at 03:19 #12

    my approach is a bit different. i use page IDs defined via server-side script to load specific page assets.
    first i setup a page ID for every page in the controller or on the head of the document:

    
    

    then use these IDs to load the asset for each page:

    <link rel="stylesheet" href="assets/css/.css">

    it's very crude. but it does work well for me to split up assets for a large application.

  13. March 19th, 2011 at 10:15 #13

    Hey Paul,

    what are the args used for? It seems to work without them at first sight.

    Regards,
    Tom

  14. March 19th, 2011 at 11:04 #14

    @Thomas Horster
    Yeah works totally fine without them…

    I just like it as a point of extension.. in case you want to do a :

    UTIL.fire('common','initalizeWidgets', widgetCodePresent() ? true : false );

    or something a little weird like that.

  15. Jeff
    March 29th, 2011 at 13:34 #15

    Awesome solution!

    Do you have any ideas how to make it so a method doesn't have to be created for each page?

    I'm using it on a site with a large number of pages. Not every page requires page-specific JavaScript, so it would be nice to only create methods for pages that need them.

  16. March 29th, 2011 at 13:44 #16

    @Jeff
    A method doesn't have to be created for each. The script is smart enough to not error if you didnt make one it's all good.

  17. Ryan
    May 1st, 2011 at 13:13 #17

    @Daniel15

    Yeah wow, why didn't you think of the wheel or the light bulb. Idiot!

  18. June 17th, 2011 at 20:49 #18

    Sometimes when people far smarter than me come up with stuff like this, I look at my cobbled-together server-side solution (translating server variables to JS variables before sending the page, etc) and think, "meh, it works."

    Other times, I read it twice, thrice, fourthce… and although I'm still not totally sussing the whole thing yet, the light starts to come on and I look at my cobbled-together server-side solution and think, "This has got to go!" This is one such time. At your suggestion, going to check out Blake waters as well. I sense an improvement in both my abilities and my application coming on…

  19. July 22nd, 2011 at 17:14 #19

    I've been using this technique almost since Mr. Irish published it. Really helps in keeping the code tidy and organized.

    Also, it pays off to have the functions stored in an object literal, as you can access them via the console, or re-trigger them, for example, if you need to run the same code in a few different sections, you can declare them for one section and then, trigger them on other sections too.

  20. July 22nd, 2011 at 19:56 #20

    Is there a better/more-correct way to do the following?

    FOO = {
      sectionA : {
        init     : function(){ ... },
      },
      sectionB : {
        init     : function(){ UTIL.fire('sectionA') }
      }
    }

    That is, if I want sectionB.init to do/be the same as sectionA.init.
    Above, I'm just firing the sectionA function using the UTIL.fire method.

    But… is there a way that I could "assign" sectionA.init to sectionB.init when creating the object literal? (like if I were assigning a value to a variable using "sectionB.init = sectionA.init")

    Sorry if I'm making nonsense :)

  21. July 24th, 2011 at 20:14 #21

    I enjoy using this technique in projects, but sometimes the larger ones can get a little hard to manage (especially if multiple devs are accessing the file and making changes at once.)

    With that in mind, I just created (literally) a project that takes this concept, and allows you to organize the code into a directory structure: https://github.com/cmawhorter/CJS

    It provides a jit.php file which will then turn your broken-out structure back into a single file.

    The code is untested, and stands as a POC as of right now, but it's pretty simple, so I'd be surprised if you couldn't use it as-is right now. Feedback welcome.

  22. August 18th, 2011 at 09:33 #22

    I'm still not totally sussing the whole thing yet, the light starts to come on and I look at my cobbled-together server-side solution! Great post!

  23. August 28th, 2011 at 08:26 #23

    Is there an official name for this pattern/technique. I like it, and use it. But what should it be referred to as?

  24. December 3rd, 2011 at 16:58 #24

    I like this method but I prefer to output the layout name im using rather than the controller's name.

  25. January 11th, 2012 at 11:40 #25

    Is it too clever or too dumb to add a some $(window).load(function(){…code…}); inside common.finalize?

    I'm thinking of this, for example:

        finalize: function(){
     
          $(window).load( function(){
     
            // Loading external scripts async
            // Twitter
            (function(d){
              var js = d.createElement('script');
              js.async = true;
              js.src = "http://platform.twitter.com/widgets.js";
              d.getElementsByTagName('head')[0].appendChild(js);
            }(document));
     
            // Alternatively, I could have used $.getScript
            // But it's the same for the sake of this example
     
          });
  26. January 22nd, 2012 at 02:15 #26

    @Julián Landerreche
    Not incorrect, but this may not be a wise idea in context of the page load times. Reasons –

    a) Browser will have to request, download and parse your script.js file entirely before it runs it, finds another external script call, and starts requesting that. It's better to put external script calls directly into the markup. Explanation

    b) Even if you go ahead with this, it might be a good idea to place the js element after the body content, not in the head. Explanation

  1. October 24th, 2010 at 22:13 | #1
  2. November 6th, 2010 at 11:32 | #2
  3. April 5th, 2011 at 13:54 | #3

For code blocks, use <pre lang="javascript">. css and html4strict are also accepted.

i left this space here for you to play. <3