KEMBAR78
Building Large jQuery Applications | PDF
http://www.flickr.com/photos/mocambique/511151694/




Building Large
jQuery Applications
Rebecca Murphey
Me

independent JavaScript developer
based in Durham, N.C.
contributor to O’Reilly’s jQuery Cookbook
co-host of the yayQuery podcast
code organization fanatic
What counts as
a large application?




http://www.flickr.com/photos/hippie/2475855465/
distinct but related pieces of functionality
extensive server communication
frequent updating of the page without reload
jQuery out of the box doesn’t give us
many tools for developing large applications;
its essentially a DOM manipulation and
Ajax library.
at doesn’t stop people from building
large applications with jQuery.
$(document).ready(function() {
    $('#myFeature li')
        .append('<div/>')
        .click(function() {
            var $this = $(this);
            var $div = $this.find('div');
            $div.load('foo.php?item=' +
                $this.attr('id'),
                function() {
                    $div.show();
                    $this.siblings()
                        .find('div').hide();
                }
            );
        })
});
// NAVIGATION
function togglePage(section) {
    // if the clicked section is already the current section AND we're in full page mode
    // minimize the current tab
    if (jQuery('#md_tab_'+ section).hasClass('current') && jQuery('#md_tab_'+ section + '
a').hasClass('md_fullpage')) {
        // alert('clicked section is current section AND fullpage mode is active; teaser should
load');
    // Minimize
        jQuery('#md_tabs_navigation a').removeClass('md_fullpage');
        jQuery('.md_body').hide();
        jQuery('#md_feature').slideDown('normal',function(){
             var bodyContent = jQuery('#md_body_'+ section);
             bodyContent.fadeOut('normal',function(){
                 jQuery('#md_tabs_navigation a').each(function(){
                     var thisSection = jQuery(this).html().replace('<span<','').replace('</
span<','');
                     var thisSection_comp = thisSection.toLowerCase().replace(' ','_');
                     jQuery('#md_body_'+ thisSection_comp).load(
                         '/app/modules/info/loadTeaser.php?sect='+ thisSection_comp,
                         function(){
                             tb_init('.md_body a.thickbox, .md_body area.thickbox, .md_body
input.thickbox');
                             bodyContent.animate({ height: 'toggle', opacity:
'toggle' },"slow");
                         }
                     );
                 });
             });
        });
        jQuery('#expandtabs span').empty().append('Expand Tabs');
    } else {
    // if the clicked section is NOT the current section OR we're NOT in full page mode
    // then let's go to full page mode and show the whole tab
How do you write an application that uses
jQuery without making a tangled mess?
e key is to think of our applications as small
pieces of functionality, and then write our code
accordingly.
Patterns & Practices

                  http://www.flickr.com/photos/docman/3270589892/
Above all else: Use jQuery as the DOM and Ajax
tool it is, not as a framework.
Use classes to organize your code; write
methods that do exactly one thing.
myApp.common.Messaging = Class.extend({
    animDuration : 500,
    hideDelay : 1500,

      init : function() {
          this.el = $('<div class="message"/>').prependTo('body').hide();
          $.subscribe('/message/notification', $.proxy(this._showMessage, this));
      },

      _showMessage : function(msg) {
          var hide = $.proxy(this._hideMessage, this);

           this.el.append('<p>' + msg + '</p>');

           if (!this.el.is(':visible')) {
               this.el.slideDown(this.animDuration, $.proxy(function() {
                    this.showTimeout = setTimeout(hide, this.hideDelay);
               }, this));
           } else {
               clearTimeout(this.showTimeout);
               this.showTimeout = setTimeout(hide, this.hideDelay);
           }
      },

      _hideMessage : function() {
          this.el.slideUp(this.animDuration);
          this._cleanup();
      },

      _cleanup : function() {
          this.el.empty();
      }
});
Use the server to send data, not markup; use a
templating system (like mustache.js) to create
markup on the client side.
myApp.widgets.Tools = Class.extend({
    itemTemplate : '<li>' +
        '<input type="checkbox" name="{{description}}" checked>' +
        '<label for="{{description}}">{{description}}</label>' +
    '</li>',

      services : [],

      init : function(el) {
          this.el = el;
          this.el.delegate('input', 'click', $.proxy(this._handleChoice, this));

           $.subscribe('/service/add', $.proxy(this._handleAdd, this));
      },

      _handleChoice : function(e) {
          var input = $(e.target),
              service = input.attr('name');

           $.publish('/service/' + service + '/toggle', [ input.is(':checked') ]);
      },

      _handleAdd : function(service) {
          if ($.inArray(service, this.services) >= 0) { return; }

           var html = Mustache.to_html(this.itemTemplate, { description : service });
           this.el.append(html);
      }
});
Use a consistent pattern for de ning
components that have a DOM representation.
var DomThinger = Class.extend({
    config : {},

      init : function(el /* jQuery object */, opts /* mixin */) {
          this.el = el;
          $.extend(this.config, opts);
      }
});
Communicate between components using a
centralized messaging hub (i.e. pub/sub).
myApp.common.Messaging = Class.extend({
    init : function() {
        this.el = $('<div class="message"/>').prependTo('body').hide();
        $.subscribe('/message/notification', $.proxy(this._showMessage, this));
    },

      _showMessage : function(msg) { /* ... */ },

      _hideMessage : function() { /* ... */ },

      _cleanup : function() { /* ... */ }
});




myApp.services._base = Class.extend({
    description : '',

      init : function(opts) {
          $.subscribe('/search/term', $.proxy(this._doSearch, this));
          $.publish('/message/notification',
             [ 'Service ' + this.description + ' added' ]
          );
      },

      _doSearch : function(term) { /* ... */ },
});
DRY and maintain separation of concerns.
$.proxy


Take control of your functions by specifying
the meaning of “this” inside of them.
myApp.services._base = Class.extend({
    description : '',

      init : function(opts) {
          $.subscribe('/search/term', $.proxy(this._doSearch, this));
          $.publish('/message/notification',
             [ 'Service ' + this.description + ' added' ]
          );
      },

      _doSearch : function(term) { /* ... */ },
});
$.fn.delegate


All the cool kids are using it
(say goodbye to $.fn.live)
myApp.widgets.Saved = Class.extend({

      init : function(el) {
          this.el = el;

           // event delegation for searching and removing searches
           this.el.delegate('li', 'click', $.proxy(this._doSearch, this));
           this.el.delegate('span.remove', 'click',
              $.proxy(this._removeSearch, this));
      },

      _doSearch : function(e) { /* ... */ },
      _removeSearch : function(e) { /* ... */ }
});
Tools for Building &
Dependency Management
              http://www.flickr.com/photos/flightsaber/2204113449/
Only a handful of solutions, none that I love.
RequireJS (building and loading)
LABjs (loading only)
modulr (building only)
Roll your own
Dojo?
Caveats
I’ve written plenty of code that didn’t work
this way. Sometimes for better, sometimes
for worse.
If your needs aren’t complex, don’t write
complex code to solve them. is approach
might be overkill for your project.
$(document).ready(function() {
    $('#myFeature li')
        .append('<div/>')
        .click(function() {
            var $this = $(this);
            var $div = $this.find('div');
            $div.load('foo.php?item=' +
                $this.attr('id'),
                function() {
                    $div.show();
                    $this.siblings()
                        .find('div').hide();
                }
            );
        })
});
is approach works really well for applications
with distinct but related pieces of functionality,
and for applications that are always evolving.
Other options for code org include plugins,
object literals, and prototypal inheritance.
anks.

rebeccamurphey.com
yayquery.com
twitter.com/rmurphey

http://pinboard.in/u:rmurphey/t:large-jquery-apps/
http://github.com/rmurphey/large-jquery-apps

Special thanks to Alex Sexton, Paul Irish, Adam Sontag, James Burke, and
Peter Higgins

Building Large jQuery Applications

  • 1.
  • 2.
    Me independent JavaScript developer basedin Durham, N.C. contributor to O’Reilly’s jQuery Cookbook co-host of the yayQuery podcast code organization fanatic
  • 3.
    What counts as alarge application? http://www.flickr.com/photos/hippie/2475855465/
  • 4.
    distinct but relatedpieces of functionality extensive server communication frequent updating of the page without reload
  • 5.
    jQuery out ofthe box doesn’t give us many tools for developing large applications; its essentially a DOM manipulation and Ajax library.
  • 6.
    at doesn’t stoppeople from building large applications with jQuery.
  • 7.
    $(document).ready(function() { $('#myFeature li') .append('<div/>') .click(function() { var $this = $(this); var $div = $this.find('div'); $div.load('foo.php?item=' + $this.attr('id'), function() { $div.show(); $this.siblings() .find('div').hide(); } ); }) });
  • 8.
    // NAVIGATION function togglePage(section){ // if the clicked section is already the current section AND we're in full page mode // minimize the current tab if (jQuery('#md_tab_'+ section).hasClass('current') && jQuery('#md_tab_'+ section + ' a').hasClass('md_fullpage')) { // alert('clicked section is current section AND fullpage mode is active; teaser should load'); // Minimize jQuery('#md_tabs_navigation a').removeClass('md_fullpage'); jQuery('.md_body').hide(); jQuery('#md_feature').slideDown('normal',function(){ var bodyContent = jQuery('#md_body_'+ section); bodyContent.fadeOut('normal',function(){ jQuery('#md_tabs_navigation a').each(function(){ var thisSection = jQuery(this).html().replace('<span<','').replace('</ span<',''); var thisSection_comp = thisSection.toLowerCase().replace(' ','_'); jQuery('#md_body_'+ thisSection_comp).load( '/app/modules/info/loadTeaser.php?sect='+ thisSection_comp, function(){ tb_init('.md_body a.thickbox, .md_body area.thickbox, .md_body input.thickbox'); bodyContent.animate({ height: 'toggle', opacity: 'toggle' },"slow"); } ); }); }); }); jQuery('#expandtabs span').empty().append('Expand Tabs'); } else { // if the clicked section is NOT the current section OR we're NOT in full page mode // then let's go to full page mode and show the whole tab
  • 9.
    How do youwrite an application that uses jQuery without making a tangled mess?
  • 10.
    e key isto think of our applications as small pieces of functionality, and then write our code accordingly.
  • 11.
    Patterns & Practices http://www.flickr.com/photos/docman/3270589892/
  • 12.
    Above all else:Use jQuery as the DOM and Ajax tool it is, not as a framework.
  • 13.
    Use classes toorganize your code; write methods that do exactly one thing.
  • 14.
    myApp.common.Messaging = Class.extend({ animDuration : 500, hideDelay : 1500, init : function() { this.el = $('<div class="message"/>').prependTo('body').hide(); $.subscribe('/message/notification', $.proxy(this._showMessage, this)); }, _showMessage : function(msg) { var hide = $.proxy(this._hideMessage, this); this.el.append('<p>' + msg + '</p>'); if (!this.el.is(':visible')) { this.el.slideDown(this.animDuration, $.proxy(function() { this.showTimeout = setTimeout(hide, this.hideDelay); }, this)); } else { clearTimeout(this.showTimeout); this.showTimeout = setTimeout(hide, this.hideDelay); } }, _hideMessage : function() { this.el.slideUp(this.animDuration); this._cleanup(); }, _cleanup : function() { this.el.empty(); } });
  • 15.
    Use the serverto send data, not markup; use a templating system (like mustache.js) to create markup on the client side.
  • 16.
    myApp.widgets.Tools = Class.extend({ itemTemplate : '<li>' + '<input type="checkbox" name="{{description}}" checked>' + '<label for="{{description}}">{{description}}</label>' + '</li>', services : [], init : function(el) { this.el = el; this.el.delegate('input', 'click', $.proxy(this._handleChoice, this)); $.subscribe('/service/add', $.proxy(this._handleAdd, this)); }, _handleChoice : function(e) { var input = $(e.target), service = input.attr('name'); $.publish('/service/' + service + '/toggle', [ input.is(':checked') ]); }, _handleAdd : function(service) { if ($.inArray(service, this.services) >= 0) { return; } var html = Mustache.to_html(this.itemTemplate, { description : service }); this.el.append(html); } });
  • 17.
    Use a consistentpattern for de ning components that have a DOM representation.
  • 18.
    var DomThinger =Class.extend({ config : {}, init : function(el /* jQuery object */, opts /* mixin */) { this.el = el; $.extend(this.config, opts); } });
  • 19.
    Communicate between componentsusing a centralized messaging hub (i.e. pub/sub).
  • 20.
    myApp.common.Messaging = Class.extend({ init : function() { this.el = $('<div class="message"/>').prependTo('body').hide(); $.subscribe('/message/notification', $.proxy(this._showMessage, this)); }, _showMessage : function(msg) { /* ... */ }, _hideMessage : function() { /* ... */ }, _cleanup : function() { /* ... */ } }); myApp.services._base = Class.extend({ description : '', init : function(opts) { $.subscribe('/search/term', $.proxy(this._doSearch, this)); $.publish('/message/notification', [ 'Service ' + this.description + ' added' ] ); }, _doSearch : function(term) { /* ... */ }, });
  • 21.
    DRY and maintainseparation of concerns.
  • 22.
    $.proxy Take control ofyour functions by specifying the meaning of “this” inside of them.
  • 23.
    myApp.services._base = Class.extend({ description : '', init : function(opts) { $.subscribe('/search/term', $.proxy(this._doSearch, this)); $.publish('/message/notification', [ 'Service ' + this.description + ' added' ] ); }, _doSearch : function(term) { /* ... */ }, });
  • 24.
    $.fn.delegate All the coolkids are using it (say goodbye to $.fn.live)
  • 25.
    myApp.widgets.Saved = Class.extend({ init : function(el) { this.el = el; // event delegation for searching and removing searches this.el.delegate('li', 'click', $.proxy(this._doSearch, this)); this.el.delegate('span.remove', 'click', $.proxy(this._removeSearch, this)); }, _doSearch : function(e) { /* ... */ }, _removeSearch : function(e) { /* ... */ } });
  • 26.
    Tools for Building& Dependency Management http://www.flickr.com/photos/flightsaber/2204113449/
  • 27.
    Only a handfulof solutions, none that I love.
  • 28.
    RequireJS (building andloading) LABjs (loading only) modulr (building only) Roll your own Dojo?
  • 29.
  • 30.
    I’ve written plentyof code that didn’t work this way. Sometimes for better, sometimes for worse.
  • 31.
    If your needsaren’t complex, don’t write complex code to solve them. is approach might be overkill for your project.
  • 32.
    $(document).ready(function() { $('#myFeature li') .append('<div/>') .click(function() { var $this = $(this); var $div = $this.find('div'); $div.load('foo.php?item=' + $this.attr('id'), function() { $div.show(); $this.siblings() .find('div').hide(); } ); }) });
  • 33.
    is approach worksreally well for applications with distinct but related pieces of functionality, and for applications that are always evolving.
  • 34.
    Other options forcode org include plugins, object literals, and prototypal inheritance.
  • 35.