Javascript Frameworks for Well Architected, Immersive Web Apps

991 views

Published on

Immersive web applications involve sophisticated interactivity within the browser, connected to models and data persistence on the server. The structure of the application is clearly delimited between client-side and server-side, but the available tools for building web applications have often blurred this distinction. The result is applications that are difficult to design and maintain.

Published in: Technology
1 Comment
4 Likes
Statistics
Notes
  • Here is a video of me giving this talk at Bar Camp Nashville:
    http://vimeo.com/30805815


    This presentation also exists as a two part video with better quality audio than the above video:

    Part I gives some background about why we need a javascript framework
    in the first place (general background for an audience that might be
    new to using a javascript framework; http://vimeo.com/30328747 ).

    Part 2 delves into a demo application, including detailed
    explanation of AngularJS controllers, resources, and templates, as
    well as where the files reside in the Rails 3.1 file hierarchy
    ( http://vimeo.com/30329977 ).
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total views
991
On SlideShare
0
From Embeds
0
Number of Embeds
4
Actions
Shares
0
Downloads
51
Comments
1
Likes
4
Embeds 0
No embeds

No notes for slide
  • \n
  • \n
  • \n
  • In a traditional model-view-controller web app, business logic is defined in the Model layer. The Controller responds to incoming requests by talking to the model layer and passing model objects to the View, which renders the objects for presentation in a browser.\n\nThe implementation of such a system is pure MVC. It is very easy to illustrate where the application executes in the client/server relationship: it all executes on the server.\n
  • In a traditional model-view-controller web app, business logic is defined in the Model layer. The Controller responds to incoming requests by talking to the model layer and passing model objects to the View, which renders the objects for presentation in a browser.\n\nThe implementation of such a system is pure MVC. It is very easy to illustrate where the application executes in the client/server relationship: it all executes on the server.\n
  • In a traditional model-view-controller web app, business logic is defined in the Model layer. The Controller responds to incoming requests by talking to the model layer and passing model objects to the View, which renders the objects for presentation in a browser.\n\nThe implementation of such a system is pure MVC. It is very easy to illustrate where the application executes in the client/server relationship: it all executes on the server.\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Javascript Frameworks for Well Architected, Immersive Web Apps

    1. 1. Javascript Frameworksfor Well Architected, Immersive Web Apps Daniel Nelson Centresource Interactive Agency
    2. 2. A description of the problem and what we are seeking in a solution
    3. 3. An example unpacked
    4. 4. Traditional MVC Web App
    5. 5. Traditional MVC Web App Model
    6. 6. Traditional MVC Web App Model Controller
    7. 7. Traditional MVC Web App Model Controller View
    8. 8. Traditional MVC Web App ModelServer Controller View
    9. 9. Traditional MVC Web App Model Controller ViewServer
    10. 10. Traditional MVC Web App Model Controller ViewServer
    11. 11. Traditional MVC Web App Model Controller ViewServer My Web App HTML
    12. 12. Traditional MVC Web App Model Controller ViewServer My Web App HTTP
    13. 13. Traditional MVC Web App Model Controller ViewServer My Web App HTML
    14. 14. Without the Refresh Model Controller ViewServer
    15. 15. Without the Refresh Model Controller ViewServer My Web App HTML+JS
    16. 16. Without the Refresh Model Controller ViewServer My Web App XHR
    17. 17. Without the Refresh Model Controller ViewServer My Web App JSON
    18. 18. How do we render the response?
    19. 19. Partials<h1>My Web App</h1><div class="blurbs"> <%= render :partial => "blurb",</div> :collection => @blurbs %> My Web App<div class="blob"> <%= render "blob" %></div>
    20. 20. Partials<h1>My Web App</h1><div class="blurbs"> <%= render :partial => "blurb",</div> :collection => @blurbs %> My Web App<div class="blob"> <%= render "blob" %></div>
    21. 21. Partialsjson_response = { :html => render(:partial => "blurb", :collection => @blurbs), My Web App :other_info => "blah"}
    22. 22. Sending HTML in the JSON Model Controller ViewServer My Web App XHR
    23. 23. Sending HTML in the JSON Model Controller ViewServer JSON My Web App with HTML
    24. 24. Is this a good solution?
    25. 25. Degrades gracefully
    26. 26. Easy to test
    27. 27. It works
    28. 28. It works(up to a point)
    29. 29. A More Accurate Picture Model Browser Controller View Logic View LogicServer My Web App XHR
    30. 30. What happens when the front end of the application becomesas sophisticated as the back end?
    31. 31. What Happened to our MVC? Browser JS Model Model JS Controller Controller View Logic View LogicServer My Web App XHR
    32. 32. A better way
    33. 33. Decouple
    34. 34. Two ApplicationsServer Browser Model Model JSON REST Controller Controller API JSON View
    35. 35. How do we achieve this?
    36. 36. Rails + Javascript Framework
    37. 37. Rails + Javascript Framework • AngularJS • Backbone.js • Batman • ExtJS/ExtDirect • Javascript MVC • Knockout.js • Spine • SproutCore
    38. 38. What are we looking for?
    39. 39. What are we looking for?in general• documentation & community• testability• ability to organize code• opinionated
    40. 40. What are we looking for?in general• documentation & community• testability• ability to organize code• opinionated
    41. 41. What are we looking for?in general in particular• documentation & community • decouple GUI from implementation logic• testability • persisting data abstracts XHR• ability to organize code • sensible routing (for deep• opinionated linking) • compatible with other tools (such as jQuery)
    42. 42. AngularJS
    43. 43. Documentation & community http://angularjs.org
    44. 44. TestabilityAngularJS comes with testing built in• Jasmine & “e2e”• every step of the angularjs.org tutorial shows how to testFits naturally into the Rails testing ecosystem• Jasmine for unit specs• RSpec (or Cucumber) + Capybara for integration specs• easier in Rails than Angular alone
    45. 45. Organization & Opinionation will become apparent as we explore the code
    46. 46. A demo app Rails 3.1 + AngularJShttp://github.com/centresource/angularjs_rails_demo
    47. 47. Everything dynamic class ApplicationController < ActionController::Baseprotect_from_forgerybefore_filter :intercept_html_requestslayout nilprivatedef intercept_html_requests render(layouts/dynamic) if request.format == Mime::HTMLenddef handle_unverified_request reset_session render "#{Rails.root}/public/500.html", :status => 500, :layout => nilend
    48. 48. views / layouts / dynamic.html.erb<!doctype html><html xmlns:ng="http://angularjs.org/"><head> <meta charset="utf-8"> <title>Angular Rails Demo</title> <%= stylesheet_link_tag "application" %> <%= csrf_meta_tag %></head><body ng:controller="PhotoGalleryCtrl"> <ng:view></ng:view> <script src="/assets/angular.min.js" ng:autobind></script> <%= javascript_include_tag "application" -%></body></html>
    49. 49. Routes/* app/assets/javascripts/controllers.js.erb */$route.when(/photographers, {template: <%= asset_path("photographers.html") %>, controller: PhotographersCtrl});$route.when(/photographers/:photographer_id/galleries, {template: <%= asset_path("galleries.html") %>, controller: GalleriesCtrl});$route.when(/photographers/:photographer_id/galleries/:gallery_id/photos, {template: <%= asset_path("photos.html") %>, controller: PhotosCtrl});$route.otherwise({redirectTo: /photographers});$route.onChange(function() { this.params = $route.current.params;});
    50. 50. AngularJS controller/* app/assets/javascripts/controllers.js.erb */function GalleriesCtrl(Galleries, Photographers) { this.photographer = Photographers.get({ photographer_id: this.params.photographer_id }); this.galleries = Galleries.index({ photographer_id: this.params.photographer_id });}
    51. 51. Data binding/* app/assets/templates/photographers.html */<h1>Galleries of {{photographer.name}}</h1><ul id="galleries"> <li class="gallery" ng:repeat="gallery in galleries"> <a href="#/photographers/{{photographer.id}}/galleries/{{gallery.id}}/photos">{{gallery.title}}</a> </li></ul>
    52. 52. Resources/* app/assets/javascripts/services.js */angular.service(Photographers, function($resource) { return $resource(photographers/:photographer_id, {}, { index: { method: GET, isArray: true }});});angular.service(Galleries, function($resource) { return $resource(photographers/:photographer_id/galleries/:gallery_id, {}, { index: { method: GET,isArray: true }});});angular.service(Photos, function($resource) { return $resource(photographers/:photographer_id/galleries/:gallery_id/photos, {}, { index: { method:GET, isArray: true }});});angular.service(SelectedPhotos, function($resource) { return $resource(selected_photos/:selected_photo_id, {}, { create: { method: POST }, index: { method: GET, isArray: true }, update: { method: PUT }, destroy: { method: DELETE }});});
    53. 53. Resources/* app/assets/javascripts/services.js.erb */ */angular.service(Photographers, function($resource) { return $resource(photographers/:photographer_id, {}, { index: { method: GET, isArray: true }});});angular.service(Galleries, function($resource) { return $resource(photographers/:photographer_id/galleries/:gallery_id, {}, { index: { method: GET,isArray: true }});});angular.service(Photos, function($resource) { return $resource(photographers/:photographer_id/galleries/:gallery_id/photos, {}, { index: { method:GET, isArray: true }});});angular.service(SelectedPhotos, function($resource) { return $resource(selected_photos/:selected_photo_id, {}, { create: { method: POST }, index: { method: GET, isArray: true }, update: { method: PUT }, destroy: { method: DELETE }});});
    54. 54. Resources/* app/assets/javascripts/services.js.erb */ */angular.service(Photographers, function($resource) { return $resource(photographers/:photographer_id, {}, { index: { method: GET, isArray: true }});}); <%= photographers_path(:photographer_id) %>angular.service(Galleries, function($resource) { return $resource(photographers/:photographer_id/galleries/:gallery_id, {}, { index: { method: GET,isArray: true }});});angular.service(Photos, function($resource) { return $resource(photographers/:photographer_id/galleries/:gallery_id/photos, {}, { index: { method:GET, isArray: true }});});angular.service(SelectedPhotos, function($resource) { return $resource(selected_photos/:selected_photo_id, {}, { create: { method: POST }, index: { method: GET, isArray: true }, update: { method: PUT }, destroy: { method: DELETE }});});
    55. 55. Resources/* app/assets/javascripts/services.js.erb */ */angular.service(Photographers, function($resource) { return $resource(photographers/:photographer_id, {}, { index: { method: GET, isArray: true }});}); <%= photographers_path(:photographer_id) %>angular.service(Galleries, function($resource) { return $resource(photographers/:photographer_id/galleries/:gallery_id, {}, { index: { method: GET, <%= photographers_galleries_path(:photographer_id, :gallery_id) %>isArray: true }});});angular.service(Photos, function($resource) { return $resource(photographers/:photographer_id/galleries/:gallery_id/photos, {}, { index: { method:GET, isArray: true }});});angular.service(SelectedPhotos, function($resource) { return $resource(selected_photos/:selected_photo_id, {}, { create: { method: POST }, index: { method: GET, isArray: true }, update: { method: PUT }, destroy: { method: DELETE }});});
    56. 56. Resources/* app/assets/javascripts/services.js.erb */ */angular.service(Photographers, function($resource) { return $resource(photographers/:photographer_id, {}, { index: { method: GET, isArray: true }});}); <%= photographers_path(:photographer_id) %>angular.service(Galleries, function($resource) { return $resource(photographers/:photographer_id/galleries/:gallery_id, {}, { index: { method: GET, <%= photographers_galleries_path(:photographer_id, :gallery_id) %>isArray: true }});});angular.service(Photos, function($resource) { <%= photographers_galleries_photos_path(:photographer_id, :gallery_id) %> return $resource(photographers/:photographer_id/galleries/:gallery_id/photos, {}, { index: { method:GET, isArray: true }});});angular.service(SelectedPhotos, function($resource) { return $resource(selected_photos/:selected_photo_id, {}, { create: { method: POST }, index: { method: GET, isArray: true }, update: { method: PUT }, destroy: { method: DELETE }});});
    57. 57. Resources/* app/assets/javascripts/services.js.erb */ */angular.service(Photographers, function($resource) { return $resource(photographers/:photographer_id, {}, { index: { method: GET, isArray: true }});}); <%= photographers_path(:photographer_id) %>angular.service(Galleries, function($resource) { return $resource(photographers/:photographer_id/galleries/:gallery_id, {}, { index: { method: GET, <%= photographers_galleries_path(:photographer_id, :gallery_id) %>isArray: true }});});angular.service(Photos, function($resource) { <%= photographers_galleries_photos_path(:photographer_id, :gallery_id) %> return $resource(photographers/:photographer_id/galleries/:gallery_id/photos, {}, { index: { method:GET, isArray: true }});});angular.service(SelectedPhotos, function($resource) { return $resource(selected_photos/:selected_photo_id, {}, { create: { method: POST }, <%= selected_photos_path(:selected_photo_id) %> index: { method: GET, isArray: true }, update: { method: PUT }, destroy: { method: DELETE }});});
    58. 58. /* app/assets/javascripts/controllers.js.erb */function PhotosCtrl(Photos, Galleries, Photographers, SelectedPhotos) { var self = this; self.photographer = Photographers.get({ photographer_id: this.params.photographer_id }); self.gallery = Galleries.get({ photographer_id: this.params.photographer_id, gallery_id:this.params.gallery_id }); self.photos = Photos.index({ photographer_id: this.params.photographer_id, gallery_id:this.params.gallery_id }); self.selected_photos = SelectedPhotos.index(); self.selectPhoto = function(photo) { var selected_photo = new SelectedPhotos({ selected_photo: { photo_id: photo.id } }); selected_photo.$create(function() { self.selected_photos.push(selected_photo); }); } self.deleteSelectedPhoto = function(selected_photo) { angular.Array.remove(self.selected_photos, selected_photo); selected_photo.$destroy({ selected_photo_id: selected_photo.id }); } self.saveSelectedPhoto = function(selected_photo) { selected_photo.$update({ selected_photo_id: selected_photo.id }); $(input).blur(); }}
    59. 59. /* app/assets/javascripts/controllers.js.erb */function PhotosCtrl(Photos, Galleries, Photographers, SelectedPhotos) { var self = this; self.photographer = Photographers.get({ photographer_id: this.params.photographer_id }); self.gallery = Galleries.get({ photographer_id: this.params.photographer_id, gallery_id:this.params.gallery_id }); self.photos = Photos.index({ photographer_id: this.params.photographer_id, gallery_id:this.params.gallery_id }); self.selected_photos = SelectedPhotos.index(); self.selectPhoto = function(photo) { var selected_photo = new SelectedPhotos({ selected_photo: { photo_id: photo.id } }); selected_photo.$create(function() { self.selected_photos.push(selected_photo); }); } self.deleteSelectedPhoto = function(selected_photo) { angular.Array.remove(self.selected_photos, selected_photo); selected_photo.$destroy({ selected_photo_id: selected_photo.id }); } self.saveSelectedPhoto = function(selected_photo) { selected_photo.$update({ selected_photo_id: selected_photo.id }); $(input).blur(); }}
    60. 60. /* app/assets/javascripts/controllers.js.erb */function PhotosCtrl(Photos, Galleries, Photographers, SelectedPhotos) { var self = this; self.photographer = Photographers.get({ photographer_id: this.params.photographer_id }); self.gallery = Galleries.get({ photographer_id: this.params.photographer_id, gallery_id:this.params.gallery_id }); self.photos = Photos.index({ photographer_id: this.params.photographer_id, gallery_id:this.params.gallery_id }); self.selected_photos = SelectedPhotos.index(); self.selectPhoto = function(photo) { var selected_photo = new SelectedPhotos({ selected_photo: { photo_id: photo.id } }); selected_photo.$create(function() { self.selected_photos.push(selected_photo); }); } self.deleteSelectedPhoto = function(selected_photo) { angular.Array.remove(self.selected_photos, selected_photo); selected_photo.$destroy({ selected_photo_id: selected_photo.id }); } self.saveSelectedPhoto = function(selected_photo) { selected_photo.$update({ selected_photo_id: selected_photo.id }); $(input).blur(); }}
    61. 61. /* app/assets/templates/photos.html */<h1>The {{gallery.title}} Gallery of {{photographer.name}}</h1><div id="outer_picture_frame"> <div id="picture_frame"> <div id="prev">&lsaquo;</div> <div id="photos" my:cycle> <div class="photo" id="photo_{{photo.id}}" ng:click="selectPhoto(photo)"ng:repeat="photo in photos"> <img ng:src="{{photo.image_large_url}}" alt="{{photo.title}}" /> <span class="title">{{photo.title}}</span> </div> </div> <div id="next">&rsaquo;</div> <span class="caption">Click a photo to add it to your collection</span> </div></div><div id="selected_frame"> <div id="selected_photos"> <div class="selected_photo" ng:repeat="selected_photo in selected_photos"> <img ng:src="{{selected_photo.image_gallery_url}}" alt="{{selected_photo.title}}" /> <span class="delete" ng:click="deleteSelectedPhoto(selected_photo)">✕</span> <form ng:submit="saveSelectedPhoto(selected_photo)"> <input ng:model="selected_photo.title" /> </form> </div> </div></div>
    62. 62. /* app/assets/templates/photos.html */<h1>The {{gallery.title}} Gallery of {{photographer.name}}</h1><div id="outer_picture_frame"> <div id="picture_frame"> <div id="prev">&lsaquo;</div> <div id="photos" my:cycle> <div class="photo" id="photo_{{photo.id}}" ng:click="selectPhoto(photo)"ng:repeat="photo in photos"> <img ng:src="{{photo.image_large_url}}" alt="{{photo.title}}" /> <span class="title">{{photo.title}}</span> </div> </div> <div id="next">&rsaquo;</div> <span class="caption">Click a photo to add it to your collection</span> </div></div><div id="selected_frame"> <div id="selected_photos"> <div class="selected_photo" ng:repeat="selected_photo in selected_photos"> <img ng:src="{{selected_photo.image_gallery_url}}" alt="{{selected_photo.title}}" /> <span class="delete" ng:click="deleteSelectedPhoto(selected_photo)">✕</span> <form ng:submit="saveSelectedPhoto(selected_photo)"> <input ng:model="selected_photo.title" /> </form> </div> </div></div>
    63. 63. /* app/assets/templates/photos.html */<h1>The {{gallery.title}} Gallery of {{photographer.name}}</h1><div id="outer_picture_frame"> <div id="picture_frame"> <div id="prev">&lsaquo;</div> <div id="photos" my:cycle> <div class="photo" id="photo_{{photo.id}}" ng:click="selectPhoto(photo)"ng:repeat="photo in photos"> <img ng:src="{{photo.image_large_url}}" alt="{{photo.title}}" /> <span class="title">{{photo.title}}</span> </div> </div> <div id="next">&rsaquo;</div> <span class="caption">Click a photo to add it to your collection</span> </div></div><div id="selected_frame"> <div id="selected_photos"> <div class="selected_photo" ng:repeat="selected_photo in selected_photos"> <img ng:src="{{selected_photo.image_gallery_url}}" alt="{{selected_photo.title}}" /> <span class="delete" ng:click="deleteSelectedPhoto(selected_photo)">✕</span> <form ng:submit="saveSelectedPhoto(selected_photo)"> <input ng:model="selected_photo.title" /> </form> </div> </div></div>
    64. 64. /* app/assets/templates/photos.html */<h1>The {{gallery.title}} Gallery of {{photographer.name}}</h1><div id="outer_picture_frame"> <div id="picture_frame"> <div id="prev">&lsaquo;</div> <div id="photos" my:cycle> <div class="photo" id="photo_{{photo.id}}" ng:click="selectPhoto(photo)"ng:repeat="photo in photos"> <img ng:src="{{photo.image_large_url}}" alt="{{photo.title}}" /> <span class="title">{{photo.title}}</span> </div> </div> <div id="next">&rsaquo;</div> <span class="caption">Click a photo to add it to your collection</span> </div></div><div id="selected_frame"> <div id="selected_photos"> <div class="selected_photo" ng:repeat="selected_photo in selected_photos"> <img ng:src="{{selected_photo.image_gallery_url}}" alt="{{selected_photo.title}}" /> <span class="delete" ng:click="deleteSelectedPhoto(selected_photo)">✕</span> <form ng:submit="saveSelectedPhoto(selected_photo)"> <input ng:model="selected_photo.title" /> </form> </div> </div></div>
    65. 65. Two way data binding<form ng:submit="saveSelectedPhoto(selected_photo)"> <input ng:model="selected_photo.title" /></form>self.saveSelectedPhoto = function(selected_photo) { selected_photo.$update({ selected_photo_id: selected_photo.id }); $(input).blur(); }
    66. 66. /* app/assets/templates/photos.html */<h1>The {{gallery.title}} Gallery of {{photographer.name}}</h1><div id="outer_picture_frame"> <div id="picture_frame"> <div id="prev">&lsaquo;</div> <div id="photos" my:cycle> <div class="photo" id="photo_{{photo.id}}" ng:click="selectPhoto(photo)"ng:repeat="photo in photos"> <img ng:src="{{photo.image_large_url}}" alt="{{photo.title}}" /> <span class="title">{{photo.title}}</span> </div> </div> <div id="next">&rsaquo;</div> <span class="caption">Click a photo to add it to your collection</span> </div></div><div id="selected_frame"> <div id="selected_photos"> <div class="selected_photo" ng:repeat="selected_photo in selected_photos"> <img ng:src="{{selected_photo.image_gallery_url}}" alt="{{selected_photo.title}}" /> <span class="delete" ng:click="deleteSelectedPhoto(selected_photo)">✕</span> <form ng:submit="saveSelectedPhoto(selected_photo)"> <input ng:model="selected_photo.title" /> </form> </div> </div></div>
    67. 67. /* app/assets/templates/photos.html */<h1>The {{gallery.title}} Gallery of {{photographer.name}}</h1><div id="outer_picture_frame"> <div id="picture_frame"> <div id="prev">&lsaquo;</div> <div id="photos" my:cycle> <div class="photo" id="photo_{{photo.id}}" ng:click="selectPhoto(photo)"ng:repeat="photo in photos"> <img ng:src="{{photo.image_large_url}}" alt="{{photo.title}}" /> <span class="title">{{photo.title}}</span> </div> </div> <div id="next">&rsaquo;</div> <span class="caption">Click a photo to add it to your collection</span> </div></div><div id="selected_frame"> <div id="selected_photos"> <div class="selected_photo" ng:repeat="selected_photo in selected_photos"> <img ng:src="{{selected_photo.image_gallery_url}}" alt="{{selected_photo.title}}" /> <span class="delete" ng:click="deleteSelectedPhoto(selected_photo)">✕</span> <form ng:submit="saveSelectedPhoto(selected_photo)"> <input ng:model="selected_photo.title" /> </form> </div> </div></div>
    68. 68. /* app/assets/javascripts/widgets.js */angular.directive("my:cycle", function(expr,el){ return function(container){ var scope = this; var lastChildID = container.children().last().attr(id); var doIt = function() { var lastID = container.children().last().attr(id); if (lastID != lastChildID) { lastChildID = lastID; $(container).cycle({ fx: fade, speed: 500, timeout: 3000, pause: 1, next: #next, prev: #prev}); } } var defer = this.$service("$defer"); scope.$onEval( function() { defer(doIt); }); }});
    69. 69. Thank Youhttp://github.com/centresource/angularjs_rails_demo @bluejade

    ×