Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Compose all the things
Mike North
Wicked Good Ember 2015
@MichaelLNorth
modernwebui.org
Modern Web UI
advertising.yahoo.com
Yahoo Ads & Data
Hi
ember-resize
ember-orientation
embe...
@MichaelLNorth
Composability
(Mike working w/ a composable system)
//TODO
• The state of ember at yahoo
• What’s composability, and why do we care?
• 4 areas where you can compose today
• S...
@MichaelLNorth
Yahoo Ads & Data
• 14 Ember Apps
• 68 Ember-focused developers
• A “flagship” app that ’s huge (70K lines J...
@MichaelLNorth
@MichaelLNorth
Composability
• Recombinant self-
contained pieces
• Built around
established contracts
and conventions
• P...
@MichaelLNorth
Why composability?
• Leverage existing code repeatedly
• Build apps in a more expressive way (DSLs)
• Oppor...
@MichaelLNorth
Great places to start
Style
Tests
Computed Properties
Components
@MichaelLNorth
Great places to startStyle
Declarative CSS
A
<div id=“myThing”>
...
</div>
#myThing {
float: left;
color: w...
@MichaelLNorth
Great places to startStyle
• Atomic CSS classes
• Expressive HTML
• Promotes consistency
Declarative CSS
<d...
@MichaelLNorth
Great places to startStyle
• You may have classes and/or attributes for
• Testing
• Style
• Behavior
Keep a...
@MichaelLNorth
Great places to start
Style
Tests
Computed Properties
Components
@MichaelLNorth
Great places to startComputed Properties
How does a computed property work?
GET
Has cached
value?
Recalcula...
@MichaelLNorth
Great places to startComputed Properties
How does a computed property work?
DEPENDENT CHANGED
Cache
X
I’ve
...
@MichaelLNorth
Great places to startComputed Properties
CPs can be thought of as filters
CP
r
g
b
#ff1a99
get() set()
(some...
@MichaelLNorth
Great places to startComputed Properties
• Ember.computed.*
Macros make this even easier
function product(p...
@MichaelLNorth
Great places to startComputed Properties
totalAmount: sum(
'subtotal',
'tipAmount',
'taxAmount',
product('d...
@MichaelLNorth
Great places to startComputed Properties
Example
@MichaelLNorth
Great places to start
Style
Tests
Computed Properties
Components
@MichaelLNorth
Great places to startComponents
• Not source of truth for state ✔
• Promotes Reuse ✔
• Recombinant ?
Compon...
@MichaelLNorth
Great places to startComponents
Looking for this
<div class="card blue-grey darken-1">
<div class="card-con...
@MichaelLNorth
Great places to startComponents
One option - lowest common element
<div class="card blue-grey darken-1">
<d...
@MichaelLNorth
Great places to startComponents
One option - lowest common element
<div class="card blue-grey darken-1">
<d...
@MichaelLNorth
Great places to startComponents
Another option - parent does everything
<div class="card blue-grey darken-1...
@MichaelLNorth
Great places to startComponents
Another option - parent does everything
<div class="card blue-grey darken-1...
@MichaelLNorth
Great places to startComponents
The expressive option
{{#wge-card title="Wicked Good Ember"}}
This will go ...
@MichaelLNorth
Great places to start
{{#wge-card
title="Wicked Good Ember"}}
This will go in the
body of the card
{{#wge-c...
@MichaelLNorth
Great places to start
{{#wge-card
title="Wicked Good Ember"}}
This will go in the
body of the card
{{#wge-c...
@MichaelLNorth
Great places to startComponents
Content projection approach
{{#wge-card
title="Wicked Good Ember"}}
This wi...
@MichaelLNorth
Great places to start
Child
export default Ember.Component.extend({
didInsertElement() {
this.nearestWithPr...
@MichaelLNorth
Great places to start
import Ember from 'ember';
const { computed: {alias, empty} } = Ember;
export default...
@MichaelLNorth
Great places to startComponents
<div class="card-content white-text">
<div class="card-title">{{title}}</di...
@MichaelLNorth
Great places to start
Style
Tests
Computed Properties
Components
@MichaelLNorth
Great places to startTests
A lot of tests are verbose and ugly
• Sensitivity to order and/or timing
• Britt...
@MichaelLNorth
Great places to startTests
test(“authorized user should end up at account's search list page”, function(ass...
@MichaelLNorth
Great places to startTests
A wild PageObject appears
• Prime pretender
• Access to controls
• Specific asser...
@MichaelLNorth
Great places to startTests
Writing PageObjects
• Return this
• To assert or not to assert?
• Build PageObje...
@MichaelLNorth
Great places to startSome final thoughts
Even more composability on the way!
• Add-ons
• Ember.Service
• Eng...
@MichaelLNorth
Conclusion
Style
Tests
Computed Properties
Components
truenorth/wge-examples
Upcoming SlideShare
Loading in …5
×

Compose all the things (Wicked Good Ember 2015)

7,249 views

Published on

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

Published in: Technology

Compose all the things (Wicked Good Ember 2015)

  1. 1. Compose all the things Mike North Wicked Good Ember 2015
  2. 2. @MichaelLNorth modernwebui.org Modern Web UI advertising.yahoo.com Yahoo Ads & Data Hi ember-resize ember-orientation ember-cpm ember-cli-materialize …and more
  3. 3. @MichaelLNorth Composability (Mike working w/ a composable system)
  4. 4. //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
  5. 5. @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
  6. 6. @MichaelLNorth
  7. 7. @MichaelLNorth Composability • Recombinant self- contained pieces • Built around established contracts and conventions • Promotes reuse What do I mean?
  8. 8. @MichaelLNorth Why composability? • Leverage existing code repeatedly • Build apps in a more expressive way (DSLs) • Opportunities for unforeseen uses!
  9. 9. @MichaelLNorth Great places to start Style Tests Computed Properties Components
  10. 10. @MichaelLNorth Great places to startStyle 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; }
  11. 11. @MichaelLNorth Great places to startStyle • Atomic CSS classes • Expressive HTML • Promotes consistency Declarative CSS <div class=“pull-left white-text"> ... </div> .pull-left { float: left; } .white-text { color: white; }
  12. 12. @MichaelLNorth Great places to startStyle • 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
  13. 13. @MichaelLNorth Great places to start Style Tests Computed Properties Components
  14. 14. @MichaelLNorth Great places to startComputed Properties How does a computed property work? GET Has cached value? Recalculate No Yes X Allows caching? Cache X XYes No ReturnX
  15. 15. @MichaelLNorth Great places to startComputed Properties How does a computed property work? DEPENDENT CHANGED Cache X I’ve changed! ViewProperty obj.get(‘val’)
  16. 16. @MichaelLNorth Great places to startComputed Properties CPs can be thought of as filters CP r g b #ff1a99 get() set() (sometimes) CP r b g #ff1a99
  17. 17. @MichaelLNorth Great places to startComputed Properties • Ember.computed.* Macros make this even easier function product(prop, coeff) { return Ember.computed(prop, { get() { return this.get(prop) * coeff; } }); }
  18. 18. @MichaelLNorth Great places to startComputed Properties totalAmount: sum( 'subtotal', 'tipAmount', 'taxAmount', product('discount', -1) ), Composable CPs can be mixed and matched ember-cpm
  19. 19. @MichaelLNorth Great places to startComputed Properties Example
  20. 20. @MichaelLNorth Great places to start Style Tests Computed Properties Components
  21. 21. @MichaelLNorth Great places to startComponents • Not source of truth for state ✔ • Promotes Reuse ✔ • Recombinant ? Components are pretty close… ember-cli-materialize
  22. 22. @MichaelLNorth Great places to startComponents 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>
  23. 23. @MichaelLNorth Great places to startComponents 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}}
  24. 24. @MichaelLNorth Great places to startComponents 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
  25. 25. @MichaelLNorth Great places to startComponents 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}}
  26. 26. @MichaelLNorth Great places to startComponents 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
  27. 27. @MichaelLNorth Great places to startComponents 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}}
  28. 28. @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
  29. 29. @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
  30. 30. @MichaelLNorth Great places to startComponents 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
  31. 31. @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
  32. 32. @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
  33. 33. @MichaelLNorth Great places to startComponents <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
  34. 34. @MichaelLNorth Great places to start Style Tests Computed Properties Components
  35. 35. @MichaelLNorth Great places to startTests 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
  36. 36. @MichaelLNorth Great places to startTests 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
  37. 37. @MichaelLNorth Great places to startTests A wild PageObject appears • Prime pretender • Access to controls • Specific asserts fillInclick currentURL andThenvisit triggerEvent $().val()$ $().click()$().trigger() setFirstName clickResetButton setAge openSettings PageObject API Ember Testing API DOMSee:
  38. 38. @MichaelLNorth Great places to startTests Writing PageObjects • Return this • To assert or not to assert? • Build PageObjects for components Example The recombinant part!
  39. 39. @MichaelLNorth Great places to startSome final thoughts Even more composability on the way! • Add-ons • Ember.Service • Engines (TBD) • Components ( {{yield}}, block params, etc…)
  40. 40. @MichaelLNorth Conclusion Style Tests Computed Properties Components truenorth/wge-examples

×