Successfully reported this slideshow.
Your SlideShare is downloading. ×

Single Page Web Applications with CoffeeScript, Backbone and Jasmine

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Loading in …3
×

Check these out next

1 of 147 Ad

More Related Content

Slideshows for you (20)

Similar to Single Page Web Applications with CoffeeScript, Backbone and Jasmine (20)

Advertisement

Recently uploaded (20)

Single Page Web Applications with CoffeeScript, Backbone and Jasmine

  1. 1. Single Page Web Applications CoffeeScript + Backbone.js + Jasmine BDD @pirelenito github.com/pirelenito #tdc2011
  2. 2. Who am I?
  3. 3. What can you expect? • What I am doing? • Why is CoffeeScript so awesome? • The era before Backbone; • Fix browser spaghetti code with Backbone; • Be professional: with Jasmine BDD;
  4. 4. Assumptions • You know your Javascript; • and your JQuery; • You developed for the web before.
  5. 5. What I am doing?
  6. 6. Fullscreen Map Geo CRUDs No refreshes
  7. 7. Fullscreen Map Geo CRUDs No refreshes CoffeeScript Backbone.js
  8. 8. Fullscreen Map Geo CRUDs No refreshes CoffeeScript Backbone.js JSON Rest API Ruby on Rails
  9. 9. Why is CoffeeScript so awesome?
  10. 10. I like to choose my tools!
  11. 11. I can to that on the Server!
  12. 12. Browser? Javascript Only!
  13. 13. Browser? Javascript Only! NO MOAR!
  14. 14. Too error-prone.
  15. 15. Semicolon insertion return return { { a: 10 a: 10 }; };
  16. 16. Semicolon insertion return ; return { { a: 10 a: 10 }; };
  17. 17. Global variables function messWithGlobal() { a = 10; }
  18. 18. Global variables function messWithGlobal() { a = 10; } messWithGlobal(); alert(a);
  19. 19. Global variables function messWithGlobal() { a = 10; } messWithGlobal(); alert(a);
  20. 20. Global variables function messWithGlobal() { a = 10; } messWithGlobal(); alert(a); function messWithGlobal() { var a = 10; }
  21. 21. } square = function(x) { return x * x; }; list = [1, 2, 3, 4, 5]; math = { root: Math.sqrt, Too verbose! square: square, cube: function(x) { return x * square(x); } }; race = function() { var runners, winner; winner = arguments[0], runners = 2 <= arguments.length ? __slice.call(arguments, 1) : []; return print(winner, runners); };
  22. 22. Awesome platform.
  23. 23. “I Think Coffeescript is clearly good stuff” Douglas Crockford Javascript Programming Style And Your Brain
  24. 24. Remember this? return return { { a: 10 a: 10 }; };
  25. 25. Coffeescript: return return a: 10 a: 10
  26. 26. Coffeescript: return return a: 10 a: 10 It has no semicolon!
  27. 27. How it fixes globals?
  28. 28. Never write var again! messWithGlobal = -> a = 10
  29. 29. Never write var again! messWithGlobal = -> a = 10 messWithGlobal = function() { var a; return a = 10; };
  30. 30. Compiles with scope a = 10
  31. 31. Compiles with scope a = 10 (function() { var a; a = 10; }).call(this);
  32. 32. You want global? Go explicit! window.messWithGlobal = -> a = 10
  33. 33. You want global? Go explicit! window.messWithGlobal = -> a = 10 (function() { window.messWithGlobal = function() { var a; return a = 10; }; }).call(this);
  34. 34. Functions as callbacks $('.button').click(function() { return alert('clicked!'); });
  35. 35. Functions as callbacks $('.button').click(function() { return alert('clicked!'); }); $('.button').click -> alert 'clicked!'
  36. 36. Block by identation if (somethingIsTrue) { complexMath = 10 * 3; }
  37. 37. Block by identation if (somethingIsTrue) { complexMath = 10 * 3; } if somethingIsTrue complexMath = 10 * 3
  38. 38. Classes class MyClass attribute: 'value' constructor: -> @objectVariable = 'Dude' myMethod: -> # this.objectVariable @objectVariable
  39. 39. Classes class MyClass attribute: 'value' constructor: -> @objectVariable = 'Dude' myMethod: -> # this.objectVariable @objectVariable object = new MyClass object.myMethod() object.objectVariable # Dude object.attribute
  40. 40. Compiled class: (function() { var MyClass; MyClass = (function() { MyClass.prototype.attribute = 'value'; function MyClass() { this.objectVariable = 'Dude'; } MyClass.prototype.myMethod = function() { return this.objectVariable; }; return MyClass; })(); }).call(this);
  41. 41. Ranges list = [1..5]
  42. 42. Ranges list = [1..5] var list; list = [1, 2, 3, 4, 5];
  43. 43. Expressions! vehicles = for i in [1..3] "Vehicle#{i}"
  44. 44. Expressions! vehicles = for i in [1..3] "Vehicle#{i}" vehicles ['Vehicle1', 'Vehicle2', 'Vehicle3']
  45. 45. More awesomeness... number = -42 if opposite math = square: (x) -> x * x race = (winner, runners...) -> print winner, runners alert "I knew it!" if elvis? squares = (math.square num for num in list)
  46. 46. Don’t use --bare
  47. 47. Namespaces are cool class namespace('views').MyView
  48. 48. Namespaces are cool class namespace('views').MyView new oncast.views.MyView()
  49. 49. Namespaces are cool class namespace('views').MyView new oncast.views.MyView() @.namespace = (name) -> @.oncast ||= {} return @.oncast[name] ||= {} if name return @.oncast
  50. 50. No more hidden bugs on my client code!
  51. 51. CoffeeScript will make you a better Javascript programer.
  52. 52. The era before Backbone.
  53. 53. Rails did the rendering Rails Coffeescript
  54. 54. Rails did the rendering Rails $('.new').load "/cars/new", prepare Coffeescript
  55. 55. Rails did the rendering Rails $('.new').load "/cars/new", prepare Coffeescript
  56. 56. Rails did the rendering Rails <form> <input name='name'> ... </form> $('.new').load "/cars/new", prepare Coffeescript
  57. 57. Rails did the rendering Rails <form> <input name='name'> ... </form> $('.new').load "/cars/new", prepare Coffeescript
  58. 58. Advantages • Simple; • Used tools we knew; • i18n.
  59. 59. Coffeescript Actions ActionActivationHandler One active at time EditRefAction ListRefAction ...
  60. 60. Mapped an action on our Rail’s Controller
  61. 61. Action Abstraction • Would handle the ‘remote’ rendering; • Initialize the rich components on the DOM; • JQuery heavy.
  62. 62. Problems?
  63. 63. One Action at a time!
  64. 64. Difficult to keep thinks in sync.
  65. 65. Difficult to keep thinks in sync. No real state on the browser.
  66. 66. Slow (web 1.0)
  67. 67. “Not surprisingly, we're finding ourselves solving similar problems repeatedly.” Henrik Joreteg http://andyet.net/blog/2010/oct/29/building-a-single-page-app-with-backbonejs-undersc/
  68. 68. Fix browser spaghetti code with Backbone.
  69. 69. Why Backbone?
  70. 70. Gives enough structure
  71. 71. Very lightweight
  72. 72. Easy to get started!
  73. 73. Why not GWT(for instance)?
  74. 74. Doesn’t hide the DOM You are doing real web development.
  75. 75. Works nicely with CoffeeScript Inheritance. https://github.com/documentcloud/backbone/blob/master/test/model.coffee
  76. 76. Works nicely with CoffeeScript Inheritance. class Car extends Backbone.Model https://github.com/documentcloud/backbone/blob/master/test/model.coffee
  77. 77. Ok, but what is it?
  78. 78. Four Abstractions • Collection; • Model; • Router; • View;
  79. 79. Models / Collections class Reference extends Backbone.Model urlRoot: "/references"
  80. 80. Models / Collections class Reference extends Backbone.Model urlRoot: "/references" class References extends Backbone.Collection model: Reference url: '/references' Model Model Model Collection
  81. 81. Fetch Collection # creates a new collection references = new References # fetch data asynchronously references.fetch()
  82. 82. Fetch Collection # creates a new collection references = new References # fetch data asynchronously references.fetch() [{ id: 1 JSON name: "Paulo's House", latitude: '10', longitude: '15' }]
  83. 83. Model attributes
  84. 84. Model attributes # get reference with id 1 reference = references.get 1
  85. 85. Model attributes # get reference with id 1 reference = references.get 1 # get the name property of the model name = reference.get 'name'
  86. 86. Model attributes # get reference with id 1 reference = references.get 1 # get the name property of the model name = reference.get 'name' # change the name property of the model reference.set name: 'new name'
  87. 87. Model attributes # get reference with id 1 reference = references.get 1 # get the name property of the model name = reference.get 'name' # change the name property of the model reference.set name: 'new name' # save the model to the server reference.save()
  88. 88. Why use model.set ? reference.set name: 'new name'
  89. 89. Events! reference.bind 'change', (name)-> alert name
  90. 90. Events! reference.bind 'change', (name)-> alert name reference.set name: 'new name'
  91. 91. Events! reference.bind 'change', (name)-> alert name reference.set name: 'new name'
  92. 92. Views class SaveButtonView extends Backbone.View className: 'save-button' tagName: 'div' events: click: '_click' render: -> $(@el).html "<input type='button' value='Save'></input>" return @ _click: -> @model.save()
  93. 93. Views class SaveButtonView extends Backbone.View className: 'save-button' tagName: 'div' events: click: '_click' render: -> $(@el).html "<input type='button' value='Save'></input>" return @ _click: -> @model.save()
  94. 94. Views class SaveButtonView extends Backbone.View className: 'save-button' tagName: 'div' events: click: '_click' render: -> $(@el).html "<input type='button' value='Save'></input>" return @ _click: -> @model.save()
  95. 95. Views class SaveButtonView extends Backbone.View className: 'save-button' tagName: 'div' events: click: '_click' render: -> $(@el).html "<input type='button' value='Save'></input>" return @ _click: -> @model.save()
  96. 96. Views class SaveButtonView extends Backbone.View className: 'save-button' tagName: 'div' events: click: '_click' render: -> $(@el).html "<input type='button' value='Save'></input>" return @ _click: -> @model.save()
  97. 97. Views class SaveButtonView extends Backbone.View className: 'save-button' tagName: 'div' events: click: '_click' render: -> $(@el).html "<input type='button' value='Save'></input>" return @ _click: -> @model.save()
  98. 98. Using a View
  99. 99. Using a View # creates a new instance of the view view = new SaveButtonView()
  100. 100. Using a View # creates a new instance of the view view = new SaveButtonView() # render it view.render()
  101. 101. Using a View # creates a new instance of the view view = new SaveButtonView() # render it view.render() # append the content of the view on the doc $('.some-div').html view.el
  102. 102. ‘Events’ is the magic element!
  103. 103. ‘Events’ is the magic element! They will keep you in sync!
  104. 104. View Model
  105. 105. Click! View Model
  106. 106. Click! event changes the model View Model
  107. 107. Click! model.set name: 'new name' event changes the model View Model
  108. 108. Click! model.set name: 'new name' event changes the model View Model event changes the view
  109. 109. Click! model.set name: 'new name' event changes the model View Model event changes the view keeps everything in sync
  110. 110. Routers class ReferencesRouter extends Backbone.Router routes: 'references': 'index' initialize: (options)-> @listPanelView = options.listPanelView index: -> @listPanelView.renderReferences()
  111. 111. Routers class ReferencesRouter extends Backbone.Router routes: 'references': 'index' initialize: (options)-> @listPanelView = options.listPanelView index: -> @listPanelView.renderReferences()
  112. 112. Routers class ReferencesRouter extends Backbone.Router routes: 'references': 'index' initialize: (options)-> @listPanelView = options.listPanelView index: -> @listPanelView.renderReferences()
  113. 113. This is just the basics!
  114. 114. Lessons Learned
  115. 115. DOM is not always your friend!
  116. 116. DOM is not always your friend! # will be 0 unless it is attached to the # document $(@el).height()
  117. 117. Always do view.render() Before $('.some-div').html view.el
  118. 118. Always return this.
  119. 119. Always return this. new SaveButtonView().highlight().render().el
  120. 120. Routers should only route!
  121. 121. Routers should only route! index: -> @listPanelView.renderReferences()
  122. 122. You don’t need an Action abstraction!!!
  123. 123. The View will represent one model/collection forever!
  124. 124. Broken events # append the view's element to the document $('.div').append view.el # remove it $('.div').empty # add it again $('.div').append view.el View events (click, change) will no longer work.
  125. 125. Collection.getOrFetch class Posts extends Backbone.Collection url: "/posts" getOrFetch: (id) -> if @get(id) post = @get id else post = new Post { id : id } @add post post.fetch() post http://bennolan.com/2011/06/10/backbone-get-or-fetch.html
  126. 126. View.fetch Backbone.View::fetch = (options={})-> return @ unless (@collection || @model) _(options).extend complete: => @removeLoading() @renderLoading() (@collection || @model).fetch options return @
  127. 127. View.fetch Backbone.View::fetch = (options={})-> return @ unless (@collection || @model) _(options).extend complete: => @removeLoading() @renderLoading() (@collection || @model).fetch options return @ view.fetch()
  128. 128. Lazy fetching class LazyFetchView extends Backbone.View render: -> if @model.isFullyFetched() # if the data is ready we render it $(@el).html ... else # otherwise we fetch the data # rendering the loading indicator @fetch() return @
  129. 129. Be professional: with Jasmine BDD.
  130. 130. Why Jasmine?
  131. 131. BDD
  132. 132. BDD rspec like!
  133. 133. given a delete button view when the user clicks the view then it should delete the model
  134. 134. Jasmine: given a delete button view when the user clicks the view then it should delete the model describe 'DeleteButtonView', -> context 'when the user clicks the view', => it 'should delete the model'
  135. 135. describe 'DeleteButton', -> context 'when the user clicks the view', => it 'should delete the model'
  136. 136. describe 'DeleteButton', -> context 'when the user clicks the view', => it 'should delete the model', => expect(@model.delete).toHaveBeenCalledOnce()
  137. 137. describe 'DeleteButton', -> context 'when the user clicks the view', => it 'should delete the model', => expect(@model.delete).toHaveBeenCalledOnce() http://sinonjs.org/ https://github.com/froots/jasmine-sinon
  138. 138. describe 'DeleteButton', -> beforeEach => @model = new Backbone.Model @deleteButton = new DeleteButton(model: @model) context 'when the user clicks the view', => it 'should delete the model', => expect(@model.delete).toHaveBeenCalledOnce()
  139. 139. describe 'DeleteButton', -> beforeEach => @model = new Backbone.Model @deleteButton = new DeleteButton(model: @model) context 'when the user clicks the view', => beforeEach => sinon.spy @model, 'delete' it 'should delete the model', => expect(@model.delete).toHaveBeenCalledOnce()
  140. 140. describe 'DeleteButton', -> beforeEach => @model = new Backbone.Model @deleteButton = new DeleteButton(model: @model) context 'when the user clicks the view', => beforeEach => sinon.spy @model, 'delete' @deleteButton.render() $(@deleteButton.el).click() it 'should delete the model', => expect(@model.delete).toHaveBeenCalledOnce()
  141. 141. Run it headless! Text http://johnbintz.github.com/jasmine-headless-webkit/
  142. 142. http://tinnedfruit.com/2011/03/03/testing-backbone-apps- with-jasmine-sinon.html
  143. 143. Further reading • https://github.com/creationix/haml-js • https://github.com/fnando/i18n-js • http://documentcloud.github.com/underscore/ • https://github.com/velesin/jasmine-jquery
  144. 144. So long and thanks for all the fish! @pirelenito github.com/pirelenito about.me/pirelenito

Editor's Notes

  • \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
  • \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

×