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

Like this? Share it with your network

Share

Building Evented Single Page Applications

  • 2,284 views
Uploaded on

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......

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.

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
2,284
On Slideshare
1,834
From Embeds
450
Number of Embeds
2

Actions

Shares
Downloads
54
Comments
0
Likes
8

Embeds 450

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

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Building Single Page Applications jQueryConf San Francisco, CA John Nunemaker April 25, 2010 Ordered List
  • 2. 1. Why? 2. What? 3. How?
  • 3. 1. Why?
  • 4. Because?
  • 5. Because? NO!
  • 6. Speed Only retrieve what changes
  • 7. Perceived Speed
  • 8. Interactivity Desktop in a browser
  • 9. But the greatest of these is... Experience
  • 10. 2. What?
  • 11. http://harmonyapp.com/
  • 12. 3. How?
  • 13. Goals
  • 14. No reloads
  • 15. No reloads History/refresh
  • 16. No reloads History/refresh Easy
  • 17. #
  • 18. No reloads History/refresh Easy
  • 19. No reloads History/refresh Easy
  • 20. No reloads History/refresh Easy
  • 21. No reloads History/refresh Easy
  • 22. The End I kid...
  • 23. No reloads
  • 24. <a href="#/items">Items</a>
  • 25. $("a[href^='#/']").live('click', function (event) { window.location.hash = $(this).attr('href'); $(document).trigger('hashchange'); return false; });
  • 26. $(document).bind('hashchange', Layout.reload);
  • 27. var Layout = { reload: function() { Layout.load(window.location.hash); } };
  • 28. 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(); } } }); } };
  • 29. 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(); } } }); } };
  • 30. var Layout = { onSuccess: function(json) { Layout.applyJSON(json); // trigger layout success }, };
  • 31. No reloads
  • 32. History/refresh
  • 33. 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);
  • 34. #/org/groups/12/45/new
  • 35. org groups 12 45 new
  • 36. org groups 12 45 new
  • 37. org groups 12 45 new
  • 38. org groups 12 45 new
  • 39. org groups 12 45 new
  • 40. org groups 12 45 new
  • 41. 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(); } };
  • 42. 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(); } };
  • 43. $(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(); }); });
  • 44. History/refresh
  • 45. Easy
  • 46. Rule #1 Abuse events for better code separation and easier customization
  • 47. $('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; });
  • 48. $('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; });
  • 49. 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);
  • 50. 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);
  • 51. { 'html': { '#content': '<h1>Heading</h1><p>Yay!</p>' }, 'replaceWith': { '#post_12': '<div class="post">...</div>' }, 'remove': ['#post_11', '#comment_12'] }
  • 52. Rule #2 The URL dictates everything
  • 53. 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]); } } };
  • 54. 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);
  • 55. Rule #3
  • 56. $('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);
  • 57. Easy
  • 58. Thank you! john@orderedlist.com @jnunemaker jQueryConf San Francisco, CA John Nunemaker April 25, 2010 Ordered List