Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ember Camp Field Notes

heycarsten
February 27, 2013

Ember Camp Field Notes

A quick summary of Ember Camp 2013 filled with inflammatory remarks and trolly goodness. Prepared for Toronto Ember.

heycarsten

February 27, 2013
Tweet

More Decks by heycarsten

Other Decks in Programming

Transcript

  1. Todos  =  Ember.Application.create(); Todos.Todo  =  Ember.Object.extend({    id:  null,  

     title:  null,    isDone:  false,    todoChanged:  function  ()  {        Todos.TodoStore.update(this);    }.observes('title',  'isDone') }); Todos.todosController  =  Ember.ArrayProxy.create({    content:  [],    createTodo:  function(title)  {        var  todo  =  Todos.Todo.create({  title:  title  }),        stats  =  document.getElementById('stats-­‐area');        this.pushObject(todo);        Todos.TodoStore.create(todo);        if  (stats.style.display=='block')  {            stats.style.display  =  'inline';        }  else  {            stats.style.display  =  'block';        }    },    pushObject:  function  (item,  ignoreStorage)  {        if  (!ignoreStorage)            Todos.TodoStore.create(item);        return  this._super(item);    }, Like this?
  2. Todos  =  Ember.Application.create(); Todos.Todo  =  Ember.Object.extend({    id:  null,  

     title:  null,    isDone:  false,    todoChanged:  function  ()  {        Todos.TodoStore.update(this);    }.observes('title',  'isDone') }); Todos.todosController  =  Ember.ArrayProxy.create({    content:  [],    createTodo:  function(title)  {        var  todo  =  Todos.Todo.create({  title:  title  }),        stats  =  document.getElementById('stats-­‐area');        this.pushObject(todo);        Todos.TodoStore.create(todo);        if  (stats.style.display=='block')  {            stats.style.display  =  'inline';        }  else  {            stats.style.display  =  'block';        }    },    pushObject:  function  (item,  ignoreStorage)  {        if  (!ignoreStorage)            Todos.TodoStore.create(item);        return  this._super(item);    },
  3. Seems bad Todos  =  Ember.Application.create(); Todos.Todo  =  Ember.Object.extend({    id:

     null,    title:  null,    isDone:  false,    todoChanged:  function  ()  {        Todos.TodoStore.update(this);    }.observes('title',  'isDone') }); Todos.todosController  =  Ember.ArrayProxy.create({    content:  [],    createTodo:  function(title)  {        var  todo  =  Todos.Todo.create({  title:  title  }),        stats  =  document.getElementById('stats-­‐area');        this.pushObject(todo);        Todos.TodoStore.create(todo);        if  (stats.style.display=='block')  {            stats.style.display  =  'inline';        }  else  {            stats.style.display  =  'block';        }    },    pushObject:  function  (item,  ignoreStorage)  {        if  (!ignoreStorage)            Todos.TodoStore.create(item);        return  this._super(item);    },
  4. Seems bad Seems bad Todos  =  Ember.Application.create(); Todos.Todo  =  Ember.Object.extend({

       id:  null,    title:  null,    isDone:  false,    todoChanged:  function  ()  {        Todos.TodoStore.update(this);    }.observes('title',  'isDone') }); Todos.todosController  =  Ember.ArrayProxy.create({    content:  [],    createTodo:  function(title)  {        var  todo  =  Todos.Todo.create({  title:  title  }),        stats  =  document.getElementById('stats-­‐area');        this.pushObject(todo);        Todos.TodoStore.create(todo);        if  (stats.style.display=='block')  {            stats.style.display  =  'inline';        }  else  {            stats.style.display  =  'block';        }    },    pushObject:  function  (item,  ignoreStorage)  {        if  (!ignoreStorage)            Todos.TodoStore.create(item);        return  this._super(item);    },
  5. Seems bad Seems bad Todos  =  Ember.Application.create(); Todos.Todo  =  Ember.Object.extend({

       id:  null,    title:  null,    isDone:  false,    todoChanged:  function  ()  {        Todos.TodoStore.update(this);    }.observes('title',  'isDone') }); Todos.todosController  =  Ember.ArrayProxy.create({    content:  [],    createTodo:  function(title)  {        var  todo  =  Todos.Todo.create({  title:  title  }),        stats  =  document.getElementById('stats-­‐area');        this.pushObject(todo);        Todos.TodoStore.create(todo);        if  (stats.style.display=='block')  {            stats.style.display  =  'inline';        }  else  {            stats.style.display  =  'block';        }    },    pushObject:  function  (item,  ignoreStorage)  {        if  (!ignoreStorage)            Todos.TodoStore.create(item);        return  this._super(item);    }, Seems bad
  6. Seems bad Seems bad Todos  =  Ember.Application.create(); Todos.Todo  =  Ember.Object.extend({

       id:  null,    title:  null,    isDone:  false,    todoChanged:  function  ()  {        Todos.TodoStore.update(this);    }.observes('title',  'isDone') }); Todos.todosController  =  Ember.ArrayProxy.create({    content:  [],    createTodo:  function(title)  {        var  todo  =  Todos.Todo.create({  title:  title  }),        stats  =  document.getElementById('stats-­‐area');        this.pushObject(todo);        Todos.TodoStore.create(todo);        if  (stats.style.display=='block')  {            stats.style.display  =  'inline';        }  else  {            stats.style.display  =  'block';        }    },    pushObject:  function  (item,  ignoreStorage)  {        if  (!ignoreStorage)            Todos.TodoStore.create(item);        return  this._super(item);    }, Seems bad Seems bad
  7. Seems bad Seems bad Todos  =  Ember.Application.create(); Todos.Todo  =  Ember.Object.extend({

       id:  null,    title:  null,    isDone:  false,    todoChanged:  function  ()  {        Todos.TodoStore.update(this);    }.observes('title',  'isDone') }); Todos.todosController  =  Ember.ArrayProxy.create({    content:  [],    createTodo:  function(title)  {        var  todo  =  Todos.Todo.create({  title:  title  }),        stats  =  document.getElementById('stats-­‐area');        this.pushObject(todo);        Todos.TodoStore.create(todo);        if  (stats.style.display=='block')  {            stats.style.display  =  'inline';        }  else  {            stats.style.display  =  'block';        }    },    pushObject:  function  (item,  ignoreStorage)  {        if  (!ignoreStorage)            Todos.TodoStore.create(item);        return  this._super(item);    }, Seems bad Seems bad Seems bad
  8. • Exemplary Ember 1.0 • Templates, Routes, Controllers, Views! Ember

    Tunes github.com/elucid/ember-tunes/tree/pre4-rebase7
  9. • Exemplary Ember 1.0 • Templates, Routes, Controllers, Views! •

    Non-trivial computed properties Ember Tunes github.com/elucid/ember-tunes/tree/pre4-rebase7
  10. • Exemplary Ember 1.0 • Templates, Routes, Controllers, Views! •

    Non-trivial computed properties • itemController (via {{each}}) Ember Tunes github.com/elucid/ember-tunes/tree/pre4-rebase7
  11. • Exemplary Ember 1.0 • Templates, Routes, Controllers, Views! •

    Non-trivial computed properties • itemController (via {{each}}) • 156 SLOC! (Backbone comes in at 290) Ember Tunes github.com/elucid/ember-tunes/tree/pre4-rebase7
  12. Talk 3 UI Patterns and Reuse or Why I Hope

    We Never Need an Ember.js Widget Library Trek Glowacki
  13. • Over 200 people have contributed • Don’t use GitHub

    issues for questions use Stack Overflow tag “ember.js”
  14. • Over 200 people have contributed • Don’t use GitHub

    issues for questions use Stack Overflow tag “ember.js” • Use topic branches for pull requests
  15. • Over 200 people have contributed • Don’t use GitHub

    issues for questions use Stack Overflow tag “ember.js” • Use topic branches for pull requests • Squash superfluous commits git  rebase  -­‐i
  16. • Over 200 people have contributed • Don’t use GitHub

    issues for questions use Stack Overflow tag “ember.js” • Use topic branches for pull requests • Squash superfluous commits git  rebase  -­‐i • Peter Wagenet is the nicest man in the world
  17. • We’re probably using observers too often • Observing property

    paths: herpDerpDidChange:  function()  {    … }.observes('herp.derp') Causes all properties to fire changes even if nothing actually changed
  18. • We’re probably using observers too often • Observing property

    paths: herpDerpDidChange:  function()  {    … }.observes('herp.derp') Causes all properties to fire changes even if nothing actually changed • Instead of observing all the things, try dealing with them where they change.
  19. • Computed Properties > Bindings in many common use-cases •

    Bindings introduce more overhead and can often be replaced with computed properties: Ember.computed.alias
  20. • Computed Properties > Bindings in many common use-cases •

    Bindings introduce more overhead and can often be replaced with computed properties: Ember.computed.alias • Computed Properties are synchronous (for now… hint.)
  21. • Integration testing is probably the ideal approach for testing

    Ember apps • But integration testing Javascript apps with Capybara is most extreme brittle
  22. • Integration testing is probably the ideal approach for testing

    Ember apps • But integration testing Javascript apps with Capybara is most extreme brittle • Konacha solves these problems
  23. • Integration testing is probably the ideal approach for testing

    Ember apps • But integration testing Javascript apps with Capybara is most extreme brittle • Konacha solves these problems • All tests run in the browser, no Ruby talking to driver, talking to DOM clusterfuckery
  24. • Integration testing is probably the ideal approach for testing

    Ember apps • But integration testing Javascript apps with Capybara is most extreme brittle • Konacha solves these problems • All tests run in the browser, no Ruby talking to driver, talking to DOM clusterfuckery • Much faster, much more reliable
  25. • Konacha is for Rails apps, but you’re smart, so

    mix Mocha and Chai yourself • Check out Jo’s slides for all the meaty deets!
  26. • Konacha is for Rails apps, but you’re smart, so

    mix Mocha and Chai yourself • Check out Jo’s slides for all the meaty deets! • slideshare.net/jo_liss/testing-ember- apps
  27. • Konacha is for Rails apps, but you’re smart, so

    mix Mocha and Chai yourself • Check out Jo’s slides for all the meaty deets! • slideshare.net/jo_liss/testing-ember- apps • github.com/jfirebaugh/konacha
  28. But, all is well! • isDirty, shouldCommit, and applyingChanges are

    coming • Ember Data is not on the back burner
  29. But, all is well! • isDirty, shouldCommit, and applyingChanges are

    coming • Ember Data is not on the back burner See, told you.
  30. • No Ember content (it’s Ember Camp!) • No apparent

    effort to become aware of how we build and deploy our apps
  31. • No Ember content (it’s Ember Camp!) • No apparent

    effort to become aware of how we build and deploy our apps • Common bro, common.
  32. <?xml  version="1.0"  encoding="UTF-­‐8"?> <root>    <dispatchList>        <element>

               <load-­‐Doop-­‐Key>DoopHerp</load-­‐Loop-­‐Key>            <load-­‐Doop-­‐Name>Herp  Derping  Doop</load-­‐Loop-­‐Name>            <load-­‐Type>DERPPING</load-­‐Type>            <timed_Data>                <element:enumType>                    <dateTime:date>2013-­‐02-­‐26T20:00:00.000Z</dateTime:date>                    <item_Parts_Elements>                        <element:enumType>                            <dateTime:date  null="true"  />                            <effectiveness_Point_Score  null="true"  />                            <derpItem_GUID>Der1</derpItem_GUID>                            <partStateString>FOUND</partStateString>                            <input  null="true"  />                            <isFound>true</isFound>                            <isNotFound>false</isNotFound>                            <outputVal>521.0</outputVal>                            <pressure  null="true"  />                            <typeOfNode>TILLER</typeOfNode>                        </element:enumType>                        <element:enumType>                            <dateTime:date  null="true"  />                            <effectiveness_Point_Score  null="true"  /> GET:  http://web-­‐1a55s.us6.herptech.biz/web4srv/WebPom/v4a5/339858873?optit…
  33. <?xml  version="1.0"  encoding="UTF-­‐8"?> <root>    <dispatchList>        <element>

               <load-­‐Doop-­‐Key>DoopHerp</load-­‐Loop-­‐Key>            <load-­‐Doop-­‐Name>Herp  Derping  Doop</load-­‐Loop-­‐Name>            <load-­‐Type>DERPPING</load-­‐Type>            <timed_Data>                <element:enumType>                    <dateTime:date>2013-­‐02-­‐26T20:00:00.000Z</dateTime:date>                    <item_Parts_Elements>                        <element:enumType>                            <dateTime:date  null="true"  />                            <effectiveness_Point_Score  null="true"  />                            <derpItem_GUID>Der1</derpItem_GUID>                            <partStateString>FOUND</partStateString>                            <input  null="true"  />                            <isFound>true</isFound>                            <isNotFound>false</isNotFound>                            <outputVal>521.0</outputVal>                            <pressure  null="true"  />                            <typeOfNode>TILLER</typeOfNode>                        </element:enumType>                        <element:enumType>                            <dateTime:date  null="true"  />                            <effectiveness_Point_Score  null="true"  /> GET:  http://web-­‐1a55s.us6.herptech.biz/web4srv/WebPom/v4a5/339858873?optit… Seems bad!
  34. • Using Rails? ActiveModel::Serializers github.com/railsapi/active-model-serializers • underscore_naming • Include root

    element: {  user:  {…}  } • id:  1,  fk_id:  1,  fk_ids:  [1,  …] • Conventions for “side-loading” data
  35. • Using Rails? ActiveModel::Serializers github.com/railsapi/active-model-serializers • underscore_naming • Include root

    element: {  user:  {…}  } • id:  1,  fk_id:  1,  fk_ids:  [1,  …] • Conventions for “side-loading” data • So what does a JSON payload look like for Ember Data using RESTAdapter?
  36. {    "user":  {        "id":  1,  

         "name":  "Carsten  Nielsen",        "shed_id":  1,        "bike_ids":  [1,  2]    },        "bikes":  [        {            "id":  1,            "kind":  "road",            "is_fixed_gear":  true,            "name":  "Urbanite"        },        {            "id":  2,            "kind":  "track",            "is_fixed_gear":  true,            "name":  "Argon  18  Electron"        }    ] } GET:  http://bikeshed.com/api/v1/users/1.json
  37. {    "user":  {        "id":  1,  

         "name":  "Carsten  Nielsen",        "shed_id":  1,        "bike_ids":  [1,  2]    },        "bikes":  [        {            "id":  1,            "kind":  "road",            "is_fixed_gear":  true,            "name":  "Urbanite"        },        {            "id":  2,            "kind":  "track",            "is_fixed_gear":  true,            "name":  "Argon  18  Electron"        }    ] } GET:  http://bikeshed.com/api/v1/users/1.json Root payload
  38. {    "user":  {        "id":  1,  

         "name":  "Carsten  Nielsen",        "shed_id":  1,        "bike_ids":  [1,  2]    },        "bikes":  [        {            "id":  1,            "kind":  "road",            "is_fixed_gear":  true,            "name":  "Urbanite"        },        {            "id":  2,            "kind":  "track",            "is_fixed_gear":  true,            "name":  "Argon  18  Electron"        }    ] } GET:  http://bikeshed.com/api/v1/users/1.json
  39. {    "user":  {        "id":  1,  

         "name":  "Carsten  Nielsen",        "shed_id":  1,        "bike_ids":  [1,  2]    },        "bikes":  [        {            "id":  1,            "kind":  "road",            "is_fixed_gear":  true,            "name":  "Urbanite"        },        {            "id":  2,            "kind":  "track",            "is_fixed_gear":  true,            "name":  "Argon  18  Electron"        }    ] } GET:  http://bikeshed.com/api/v1/users/1.json Sideloaded payload
  40. {    "user":  {        "id":  1,  

         "name":  "Carsten  Nielsen",        "shed_id":  1,        "bike_ids":  [1,  2]    },        "bikes":  [        {            "id":  1,            "kind":  "road",            "is_fixed_gear":  true,            "name":  "Urbanite"        },        {            "id":  2,            "kind":  "track",            "is_fixed_gear":  true,            "name":  "Argon  18  Electron"        }    ] } GET:  http://bikeshed.com/api/v1/users/1.json
  41. {    "user":  {        "id":  1,  

         "name":  "Carsten  Nielsen",        "shed_id":  1,        "bike_ids":  [1,  2]    },        "bikes":  [        {            "id":  1,            "kind":  "road",            "is_fixed_gear":  true,            "name":  "Urbanite"        },        {            "id":  2,            "kind":  "track",            "is_fixed_gear":  true,            "name":  "Argon  18  Electron"        }    ] } GET:  http://bikeshed.com/api/v1/users/1.json Belongs to
  42. {    "user":  {        "id":  1,  

         "name":  "Carsten  Nielsen",        "shed_id":  1,        "bike_ids":  [1,  2]    },        "bikes":  [        {            "id":  1,            "kind":  "road",            "is_fixed_gear":  true,            "name":  "Urbanite"        },        {            "id":  2,            "kind":  "track",            "is_fixed_gear":  true,            "name":  "Argon  18  Electron"        }    ] } GET:  http://bikeshed.com/api/v1/users/1.json
  43. {    "user":  {        "id":  1,  

         "name":  "Carsten  Nielsen",        "shed_id":  1,        "bike_ids":  [1,  2]    },        "bikes":  [        {            "id":  1,            "kind":  "road",            "is_fixed_gear":  true,            "name":  "Urbanite"        },        {            "id":  2,            "kind":  "track",            "is_fixed_gear":  true,            "name":  "Argon  18  Electron"        }    ] } GET:  http://bikeshed.com/api/v1/users/1.json Has many
  44. • You can also embed full payloads in relationships {

       "user":  {        "id":  1,        "name":  "Carsten  Nielsen",        "shed_id":  1,        "bikes":  [            {                "id":  1,                "kind":  "road",                "is_fixed_gear":  true,                "name":  "Urbanite"            },            {                "id":  2,                "kind":  "road",                "is_fixed_gear":  true,                "name":  "Argon  18  Electron"            }        ]    } }
  45. • The adapter needs to know about this //  Only

     on  GET DS.Adapter.map('Bikeshed.User',  {    bikes:  {  embedded:  'load'  } });
  46. • The adapter needs to know about this //  Only

     on  GET DS.Adapter.map('Bikeshed.User',  {    bikes:  {  embedded:  'load'  } }); //  GET,  PUT,  and  POST DS.Adapter.map('Bikeshed.User',  {    bikes:  {  embedded:  'always'  } });
  47. App.User.reopen({    validations:  {        firstName:  {  

             presence:  true,            length:  {  minimum:  5  }        },                age:  {            numericality:  true        }    } }); • Ember Validations
  48. • ClientSideValidatons • Introspects server-side models for validation info and

    shares that with the client • Directly consumed by ember-validations
  49. • ClientSideValidatons • Introspects server-side models for validation info and

    shares that with the client • Directly consumed by ember-validations • So DRY, it hurts.
  50. • Ember EasyForm {{#formFor  model}}    {{input  firstName}}    {{input

     lastName}}    {{input  bio  as="text"}} {{/formFor}}
  51. • Ember EasyForm {{#formFor  model}}    {{input  firstName}}    {{input

     lastName}}    {{input  bio  as="text"}} {{/formFor}}