Building Evented Single Page Applications
Upcoming SlideShare
Loading in...5
×
 

Building Evented Single Page Applications

on

  • 2,242 views

Building single page applications has always seemed like a dark art. Guess what? It's not. The key is really simple—let the URL dictate everything. I'll show how to use the window location's hash in ...

Building single page applications has always seemed like a dark art. Guess what? It's not. The key is really simple—let the URL dictate everything. I'll show how to use the window location's hash in combination with jQuery's event system to drive your entire application, from link clicks to form submissions to history management and beyond.

Statistics

Views

Total Views
2,242
Views on SlideShare
1,792
Embed Views
450

Actions

Likes
8
Downloads
54
Comments
0

2 Embeds 450

http://speakerrate.com 448
http://www.slideshare.net 2

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Building Evented Single Page Applications Building Evented Single Page Applications Presentation Transcript

  • Building Single Page Applications jQueryConf San Francisco, CA John Nunemaker April 25, 2010 Ordered List
  • 1. Why? 2. What? 3. How?
  • 1. Why?
  • Because?
  • Because? NO!
  • Speed Only retrieve what changes
  • Perceived Speed
  • Interactivity Desktop in a browser
  • But the greatest of these is... Experience
  • 2. What?
  • http://harmonyapp.com/
  • 3. How?
  • Goals
  • No reloads
  • No reloads History/refresh
  • No reloads History/refresh Easy
  • #
  • No reloads History/refresh Easy
  • No reloads History/refresh Easy
  • No reloads History/refresh Easy
  • No reloads History/refresh Easy
  • The End I kid...
  • No reloads
  • <a href="#/items">Items</a>
  • $("a[href^='#/']").live('click', function (event) { window.location.hash = $(this).attr('href'); $(document).trigger('hashchange'); return false; });
  • $(document).bind('hashchange', Layout.reload);
  • var Layout = { reload: function() { Layout.load(window.location.hash); } };
  • var Layout = { load: function(path, options) { path = path.replace(/^#/, ''); // trigger path loading events $.ajax({ url : path, dataType : 'json', success : function(json) { Layout.onSuccess(json); // trigger path success events if (options && options.success) { options.success(); } }, complete : function() { if (options && options.complete) { options.complete(); } } }); } };
  • var Layout = { load: function(path, options) { path = path.replace(/^#/, ''); $(document).trigger('path:loading', [path]); $(document).trigger('path:loading:' + path); $.ajax({ url: path, dataType: 'json', success: function(json) { Layout.onSuccess(json); $(document).trigger('path:success', [path, json]); $(document).trigger('path:success:' + path, [json]); if (options && options.success) { options.success(); } }, complete: function() { if (options && options.complete) { options.complete(); } } }); } };
  • var Layout = { onSuccess: function(json) { Layout.applyJSON(json); // trigger layout success }, };
  • No reloads
  • History/refresh
  • setInterval(function() { var hash_is_new = window.location.hash && window.currentHash != window.location.hash; if (hash_is_new) { window.currentHash = window.location.hash; Layout.handlePageLoad(); } }, 300);
  • #/org/groups/12/45/new
  • org groups 12 45 new
  • org groups 12 45 new
  • org groups 12 45 new
  • org groups 12 45 new
  • org groups 12 45 new
  • org groups 12 45 new
  • var Layout = { handlePageLoad: function() { var segments = window.location.hash.replace(/^#//, '').split('/'), total = segments.length, path = ''; function loadSectionsInOrder() { var segment = segments.shift(); path += '/' + segment; var onComplete = function() { var loaded = total - segments.length, finished = loaded == total; if (!finished) { loadSectionsInOrder(); } }; Layout.load(path, {complete: onComplete}); } loadSectionsInOrder(); } };
  • var Layout = { handlePageLoad: function() { var segments = window.location.hash.replace(/^#//, '').split('/'), total = segments.length, path = ''; $(document).trigger('page:loading'); function loadSectionsInOrder() { var segment = segments.shift(); path += '/' + segment; var onComplete = function() { var loaded = total - segments.length, finished = loaded == total; $(document).trigger('page:progress', [total, loaded]); if (finished) { $(document).trigger('page:loaded'); } else { loadSectionsInOrder(); } }; Layout.load(path, {complete: onComplete}); } loadSectionsInOrder(); } };
  • $(document).bind('page:loading', function() { $('#harmony_loading').show(); $('#loading_progress').css('width', 0); }); $(document).bind('page:progress', function(e, total, loaded) { if (total != loaded) { var final_width = 114, new_width = (loaded/total) * final_width; $('#loading_progress').animate({width: new_width+'px'}, 200); } }); $(document).bind('page:loaded', function() { $('#loading_progress').animate({width:'114px'}, 300, 'linear', function() { $('#harmony_loading').hide(); }); });
  • History/refresh
  • Easy
  • Rule #1 Abuse events for better code separation and easier customization
  • $('form').live('submit', function(event) { var $form = $(this); $form.ajaxSubmit({ dataType: 'json', beforeSend: function() { // trigger before send }, success: function(json) { // if errors, show them, else apply json }, error: function(response, status, error) { // trigger error }, complete: function() { // trigger complete } }); return false; });
  • $('form').live('submit', function(event) { var $form = $(this); $form.ajaxSubmit({ dataType: 'json', beforeSend: function() { $form.trigger('form:beforeSend'); }, success: function(json) { if (json.errors) { $form.showErrors(json.errors); } else { Layout.onSuccess(json); $form.trigger('form:success', [json]); } }, error: function(response, status, error) { $form.trigger('form:error', [response, status, error]); }, complete: function() { $form.trigger('form:complete'); } }); return false; });
  • var Site = { onCreateSuccess: function(event, json) { Sidebar.highlight($('#site_' + json.id)) Layout.updateHashWithoutLoad(window.location.hash.replace(/new$/, json.id)); }, onUpdateSuccess: function(event, json) { Sidebar.highlight($('#site_' + json.id)) } }; $('form.new_site').live('form:success', Site.onCreateSuccess); $('form.edit_site').live('form:success', Site.onUpdateSuccess);
  • var Field = { onCreateSuccess: function(event, json) { $('#list_section_' + json.section_id) .addSectionField(json.field_title.toLowerCase()); }, onUpdateSuccess: function(event, json) { $('#list_section_' + json.section_id) .removeSectionField(json.old_title.toLowerCase()) .addSectionField(json.new_title.toLowerCase()); } }; $('form.new_field') .live('form:success', Field.onCreateSuccess); $('form.edit_field').live('form:success', Field.onUpdateSuccess);
  • { 'html': { '#content': '<h1>Heading</h1><p>Yay!</p>' }, 'replaceWith': { '#post_12': '<div class="post">...</div>' }, 'remove': ['#post_11', '#comment_12'] }
  • Rule #2 The URL dictates everything
  • var Layout = { livePath: function(event, path, callback) { if (typeof(test) === 'string') { $(document).bind('path:' + event + ':' + path, callback); } else { Layout.live_path_regex[event].push([path, callback]); } } };
  • Layout.livePath('loading', //org/([^/]+)([0-9/]+).*/, Groups.loading); Layout.livePath('loading', //sections/([0-9]+)$/, Sections.markCurrent); Layout.livePath('success', '/org/groups', Groups.setup); Layout.livePath('success', '/sections', Sections.makeSortable); Layout.livePath('success', //admin/assets(.*)/, Assets.init); Layout.livePath('success', //admin/items/(d+)/, ItemForm.init); Layout.livePath('success', //admin/items/([0-9/]+)/, Items.manageSidebar);
  • Rule #3
  • $('a.field_form_toggler') .live('click', Fields.toggleAddFieldForm); $('form.new_section') .live('form:success', SectionForm.onCreateSuccess); $('form.edit_section') .live('form:success', SectionForm.onUpdateSuccess); $('form.new_field') .live('form:success', Field.onCreateSuccess); $('form.edit_field') .live('form:success', Field.onUpdateSuccess); $('a.field_destroy') .live('destroy:success', Field.onDestroySuccess);
  • Easy
  • Thank you! john@orderedlist.com @jnunemaker jQueryConf San Francisco, CA John Nunemaker April 25, 2010 Ordered List