Date post: | 07-Dec-2014 |
Category: |
Technology |
Upload: | steve-smith |
View: | 1,411 times |
Download: | 2 times |
Ordered ListJohn NunemakerjQueryConf San Francisco, CA
April 25, 2010
BuildingSingle Page Applications
1. Why?2. What?3. How?
1. Why?
Because?
Because? NO!
SpeedOnly retrieve what changes
Perceived Speed
InteractivityDesktop in a browser
ExperienceBut the greatest of these is...
2. What?
http://harmonyapp.com/
3. How?
Goals
No reloads
No reloadsHistory/refresh
No reloadsHistory/refreshEasy
#
No reloadsHistory/refreshEasy
No reloadsHistory/refreshEasy
No reloadsHistory/refreshEasy
No reloadsHistory/refreshEasy
The EndI 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 #1Abuse 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 #2The 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
Ordered List
Thank [email protected]
John NunemakerjQueryConf San Francisco, CAApril 25, 2010
@jnunemaker