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

Compose All the Things (Wicked Good Ember 2015)

Compose All the Things (Wicked Good Ember 2015)

At Wicked Good Ember 2015, I discuss composability patterns in Ember.js and modern web development in general. Detailed case studies are examined in the areas of CSS, Computed Properties, Components and Testing

Mike North

June 16, 2015
Tweet

More Decks by Mike North

Other Decks in Technology

Transcript

  1. @MichaelLNorth modernwebui.org Modern Web UI advertising.yahoo.com Yahoo Ads & Data

    Hi ember-resize ember-orientation ember-cpm ember-cli-materialize …and more
  2. //TODO • The state of ember at yahoo • What’s

    composability, and why do we care? • 4 areas where you can compose today • Style • CPMs • Components • Tests
  3. @MichaelLNorth Yahoo Ads & Data • 14 Ember Apps •

    68 Ember-focused developers • A “flagship” app that ’s huge (70K lines JS) • An internal collection of add ons Ember @ Yahoo
  4. @MichaelLNorth Composability • Recombinant self- contained pieces • Built around

    established contracts and conventions • Promotes reuse What do I mean?
  5. @MichaelLNorth Why composability? • Leverage existing code repeatedly • Build

    apps in a more expressive way (DSLs) • Opportunities for unforeseen uses!
  6. @MichaelLNorth Great places to start Style Declarative CSS A <div

    id=“myThing”> ... </div> #myThing { float: left; color: white; } B <div class=“pull-left white-text"> ... </div> .pull-left { float: left; } .white-text { color: white; }
  7. @MichaelLNorth Great places to start Style • Atomic CSS classes

    • Expressive HTML • Promotes consistency Declarative CSS <div class=“pull-left white-text"> ... </div> .pull-left { float: left; } .white-text { color: white; }
  8. @MichaelLNorth Great places to start Style • You may have

    classes and/or attributes for • Testing • Style • Behavior Keep attributes & classes organized <input class="first-name large-input” data-autoid="first-name" /> .large-input { font-size: 32px; } style fillIn(‘input[data-autoid="first-name"]', 'Mike'); testing
  9. @MichaelLNorth Great places to start Computed Properties How does a

    computed property work? GET Has cached value? Recalculate No Yes X Allows caching? Cache X X Yes No Return X
  10. @MichaelLNorth Great places to start Computed Properties How does a

    computed property work? DEPENDENT CHANGED Cache X I’ve changed! View Property obj.get(‘val’)
  11. @MichaelLNorth Great places to start Computed Properties CPs can be

    thought of as filters CP r g b #ff1a99 get() set() (sometimes) CP r b g #ff1a99
  12. @MichaelLNorth Great places to start Computed Properties • Ember.computed.* Macros

    make this even easier function product(prop, coeff) { return Ember.computed(prop, { get() { return this.get(prop) * coeff; } }); }
  13. @MichaelLNorth Great places to start Computed Properties totalAmount: sum( 'subtotal',

    'tipAmount', 'taxAmount', product('discount', -1) ), Composable CPs can be mixed and matched ember-cpm
  14. @MichaelLNorth Great places to start Components • Not source of

    truth for state ✔ • Promotes Reuse ✔ • Recombinant ? Components are pretty close… ember-cli-materialize
  15. @MichaelLNorth Great places to start Components Looking for this <div

    class="card blue-grey darken-1"> <div class="card-content white-text"> <div class="card-title"> Wicked Good Ember </div> This will go in the body of the card </div> <div class="card-action"> <a> <span {{action “accept”}}>Accept</span> </a> <a> <span {{action “cancel”}}>Cancel</span> </a> </div> </div>
  16. @MichaelLNorth Great places to start Components One option - lowest

    common element <div class="card blue-grey darken-1"> <div class="card-content white-text"> <div class="card-title"> Wicked Good Ember </div> This will go in the body of the card </div> <div class="card-action"> <a> <span {{action “accept”}}>Accept</span> </a> <a> <span {{action “cancel”}}>Cancel</span> </a> </div> </div> {{#wge-card}} <div class="card-content white-text"> <div class="card-title"> Wicked Good Ember </div> This will go in the body of the card </div> <div class="card-action"> {{#wge-card-action}} <span {{action "accept"}}> Accept </span> {{/wge-card-action}} {{#wge-card-action}} <span {{action "cancel"}}> Cancel </span> {{/wge-card-action}} </div> {{/wge-card}}
  17. @MichaelLNorth Great places to start Components One option - lowest

    common element <div class="card blue-grey darken-1"> <div class="card-content white-text"> <div class="card-title"> Wicked Good Ember </div> This will go in the body of the card </div> <div class="card-action"> <a> <span {{action “accept”}}>Accept</span> </a> <a> <span {{action “cancel”}}>Cancel</span> </a> </div> </div> {{#wge-card}} <div class="card-content white-text"> <div class="card-title"> Wicked Good Ember </div> This will go in the body of the card </div> <div class="card-action"> {{#wge-card-action}} <span {{action "accept"}}> Accept </span> {{/wge-card-action}} {{#wge-card-action}} <span {{action "cancel"}}> Cancel </span> {{/wge-card-action}} </div> {{/wge-card}} Not Useful
  18. @MichaelLNorth Great places to start Components Another option - parent

    does everything <div class="card blue-grey darken-1"> <div class="card-content white-text"> <div class="card-title"> Wicked Good Ember </div> This will go in the body of the card </div> <div class="card-action"> <a> <span {{action “accept”}}>Accept</span> </a> <a> <span {{action “cancel”}}>Cancel</span> </a> </div> </div> {{#wge-card title="Wicked Good Ember" cardActions=myCardActions}} This will go in the body of the card {{/wge-card}}
  19. @MichaelLNorth Great places to start Components Another option - parent

    does everything <div class="card blue-grey darken-1"> <div class="card-content white-text"> <div class="card-title"> Wicked Good Ember </div> This will go in the body of the card </div> <div class="card-action"> <a> <span {{action “accept”}}>Accept</span> </a> <a> <span {{action “cancel”}}>Cancel</span> </a> </div> </div> {{#wge-card title="Wicked Good Ember" cardActions=myCardActions}} This will go in the body of the card {{/wge-card}} Not Composable
  20. @MichaelLNorth Great places to start Components The expressive option {{#wge-card

    title="Wicked Good Ember"}} This will go in the body of the card {{#wge-card-action}} <span {{action "accept"}}>Accept</span> {{/wge-card-action}} {{#wge-card-action}} <span {{action "cancel"}}>Cancel</span> {{/wge-card-action}} {{/wge-card}}
  21. @MichaelLNorth Great places to start {{#wge-card title="Wicked Good Ember"}} This

    will go in the body of the card {{#wge-card-action}} <span {{action “accept"}}> Accept </span> {{/wge-card-action}} {{#wge-card-action}} <span {{action “cancel”}}> Cancel </span> {{/wge-card-action}} {{/wge-card}} Components Content projection - ruh roh <div class="card blue-grey darken-1"> <div class="card-content white-text"> <div class="card-title"> Wicked Good Ember </div> This will go in the body of the card </div> <div class="card-action"> <a> <span {{action “accept”}}>Accept</span> </a> <a> <span {{action “cancel”}}>Cancel</span> </a> </div> </div> 4 distinct pieces of content
  22. @MichaelLNorth Great places to start {{#wge-card title="Wicked Good Ember"}} This

    will go in the body of the card {{#wge-card-action}} <span {{action “accept"}}> Accept </span> {{/wge-card-action}} {{#wge-card-action}} <span {{action “cancel”}}> Cancel </span> {{/wge-card-action}} {{/wge-card}} Components Content projection - ruh roh <div class="card blue-grey darken-1"> <div class="card-content white-text"> <div class="card-title"> Wicked Good Ember </div> This will go in the body of the card </div> <div class="card-action"> <a> <span {{action “accept”}}>Accept</span> </a> <a> <span {{action “cancel”}}>Cancel</span> </a> </div> </div> 4 distinct pieces of content Sub-components project into parent {{yield}} Simple property binding
  23. @MichaelLNorth Great places to start Components Content projection approach {{#wge-card

    title="Wicked Good Ember"}} This will go in the body of the card {{#wge-card-action}} <span {{action “accept"}}> Accept </span> {{/wge-card-action}} {{#wge-card-action}} <span {{action “cancel”}}> Cancel </span> {{/wge-card-action}} {{/wge-card}} • Child components won’t render directly • Parent will handle rendering of children • Register/unregister to parent
  24. @MichaelLNorth Great places to start Child export default Ember.Component.extend({ didInsertElement()

    { this.nearestWithProperty('_wgeCard') .registerWgeAction(this); }, willDestroyElement() { this.nearestWithProperty('_wgeCard') .unregisterWgeAction(this); }, render() {} // Don't render }); const { computed: {alias, empty} } = Ember; export default Ember.Component.extend({ classNames: ['card'], _wgeCard: true, _cardActions: [], // Needed to ensure context of // child component templates // is the controller parentController: alias('targetObject'), registerWgeAction(component) { this.get('_cardActions') .addObject(component); }, unregisterWgeAction(component) { this.get('_cardActions') .removeObject(component); } }); Parent Components
  25. @MichaelLNorth Great places to start import Ember from 'ember'; const

    { computed: {alias, empty} } = Ember; export default Ember.Component.extend({ classNames: ['card'], _wgeCard: true, _cardActions: [], // Needed to ensure context of // child component templates // is the controller parentController: alias('targetObject'), registerWgeAction(component) { this.get('_cardActions') .addObject(component); }, unregisterWgeAction(component) { this.get('_cardActions') .removeObject(component); } }); Parent <div class="card-content white-text"> <div class="card-title">{{title}}</div> {{yield}} </div> {{#if _cardActions.length}} <div class="card-action"> {{#each _cardActions as |cardAction|}} {{view Ember.View tagName='a' template=cardAction.template controller=parentController}} {{/each}} </div> {{/if}} Components Parent.hbs
  26. @MichaelLNorth Great places to start Components <div class="card-content white-text"> <div

    class="card-title">{{title}}</div> {{yield}} </div> {{#if _cardActions.length}} <div class="card-action"> {{#each _cardActions as |cardAction|}} {{view Ember.View tagName='a' template=cardAction.template controller=parentController}} {{/each}} </div> {{/if}} “Captured” template Needed for actions & bindings
  27. @MichaelLNorth Great places to start Tests A lot of tests

    are verbose and ugly • Sensitivity to order and/or timing • Brittle selectors to interact with the DOM • 1 change —> break N tests
  28. @MichaelLNorth Great places to start Tests test(“authorized user should end

    up at account's search list page”, function(assert) { server.get(`${apiHost.url}/me`, json(200, me)); server.get(`${apiHost.url}/campaigns/:id`, json(200, campaign1)); server.get(`${apiHost.url}/seats/2`, json(200, seat2)); server.get(`${apiHost.url}/seats/1`, json(200, seat1)); server.get(`${apiHost.url}/account/:id`, json(200, account1)); server.get(`${apiHost.url}/breadcrumbs`, json(200, breadcrumbs.campaign)); visit('/app/account/1/campaigns'); andThen(function() { assert.equal(currentPath(), ‘app.account.campaign', 'Current url is search list page for campaig assert.equal(Ember.$('.top-navbar .brand-logo .active-title').text().trim(), campaign1.campaign assert.equal(Ember.$('.resource-tiles-container .card').length, campaigns.campaigns.length, 'On assert.deepEqual(Ember.$('.resource-tiles-container .resource-tile:first-child .card .card-cont ['Created', 'Updated'], 'Columns are correct'); assert.equal(Ember.$('.new-campaign-button').length, 1, 'New Campaign button is on the screen') }); }); A lot of tests are verbose and ugly
  29. @MichaelLNorth Great places to start Tests A wild PageObject appears

    • Prime pretender • Access to controls • Specific asserts fillIn click currentURL andThen visit triggerEvent $().val() $ $().click() $().trigger() setFirstName clickResetButton setAge openSettings PageObject API Ember Testing API DOM See:
  30. @MichaelLNorth Great places to start Tests Writing PageObjects • Return

    this • To assert or not to assert? • Build PageObjects for components Example The recombinant part!
  31. @MichaelLNorth Great places to start Some final thoughts Even more

    composability on the way! • Add-ons • Ember.Service • Engines (TBD) • Components ( {{yield}}, block params, etc…)