Upload
stephen-vance
View
203
Download
0
Embed Size (px)
Citation preview
Aesthetics-Driven Development: Cool Features for Ergonomic
Software DesignBoston Ember.js
September 8, 2016 Stephen Vance
1
Motivation• Using Bootstrap
• Relying on responsive behavior of navbars
• Some of the behavior relies on JavaScript
• Wanted more Ember-y approach
• ember-bootstrap didn’t support it yet
2
Bootstrap Navbar<nav class="navbar navbar-default" role="navigation"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class=“navbar-toggle" data-toggle="collapse" data-target=".navbar-mwpc-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> {{#link-to "index" class="navbar-brand"}}{{siteBrand.brand}}{{/link-to}} </div>
<div class="collapse navbar-collapse navbar-mwpc-collapse"> <ul class="nav navbar-nav"> <li class="dropdown"> <a href="#" class=“dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"> Service Directory<span class="caret"></span> </a> <ul class="dropdown-menu" role="menu"> {{#each-in serviceCategories.categories as | category info |}} <li> {{link-to info.displayName "service" category}} </li> {{/each-in}} </ul> </li> <li> {{#link-to "resources"}}Local Resources{{/link-to}} </li>
4
Bootstrap Navbar<nav class="navbar navbar-default" role="navigation"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class=“navbar-toggle" data-toggle="collapse" data-target=".navbar-mwpc-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> {{#link-to "index" class="navbar-brand"}}{{siteBrand.brand}}{{/link-to}} </div>
<div class="collapse navbar-collapse navbar-mwpc-collapse"> <ul class="nav navbar-nav"> <li class="dropdown"> <a href="#" class=“dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"> Service Directory<span class="caret"></span> </a> <ul class="dropdown-menu" role="menu"> {{#each-in serviceCategories.categories as | category info |}} <li> {{link-to info.displayName "service" category}} </li> {{/each-in}} </ul> </li> <li> {{#link-to "resources"}}Local Resources{{/link-to}} </li>
NavbarHeader
Toggle
Content
Nav
Brand
4
Design Concerns• Peer components shouldn’t reference each other • Navbar state should be within the component
• Outside would require users to define it • Nearest common parent
• Support multiple navbars in a page • Strive for clean DSL • Minimize exposed plumbing • Principle of Least Astonishment
7
Implementation Concerns
• Component block form • Nature of problem doesn’t require inline
• Component isolation makes it harder for related components to cooperate transparently
8
First Cut 😳{{#bs-navbar}} {{#bs-navbar-header}} {{!-- TODO: Create bs-navbar-toggle? --}} {{#bs-button toggle=true active=expanded action=toggle class="navbar-toggle collapsed"}} <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> {{/bs-button}} {{!-- TODO {{#bs-navbar-brand}}Brand{{/bs-navbar-brand}} --}} <a class="navbar-brand" href="#">Brand</a> {{/bs-navbar-header}} {{!-- TODO: Create bs-navbar-content instead of using bs-collapse --}} {{#bs-collapse collapse=collapsed class=(if expanded "collapse navbar-collapse in" "collapse navbar-collapse")}} {{#bs-nav type=type.id justified=justified stacked=stacked navbar=true}} {{#bs-nav-item}}{{#link-to "alert"}}Alert{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "button"}}Buttons{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "dropdown"}}Dropdown{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "forms"}}Forms{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "accordion"}}Accordion{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "collapse"}}Collapse{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "modal"}}Modals{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "progress"}}Progress bars{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "navs"}}Navs{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "navbars"}}Navbars{{/link-to}}{{/bs-nav-item}} {{/bs-nav}} {{/bs-collapse}} {{/bs-navbar}}
9
First Cut 😳{{#bs-navbar}} {{#bs-navbar-header}} {{!-- TODO: Create bs-navbar-toggle? --}} {{#bs-button toggle=true active=expanded action=toggle class="navbar-toggle collapsed"}} <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> {{/bs-button}} {{!-- TODO {{#bs-navbar-brand}}Brand{{/bs-navbar-brand}} --}} <a class="navbar-brand" href="#">Brand</a> {{/bs-navbar-header}} {{!-- TODO: Create bs-navbar-content instead of using bs-collapse --}} {{#bs-collapse collapse=collapsed class=(if expanded "collapse navbar-collapse in" "collapse navbar-collapse")}} {{#bs-nav type=type.id justified=justified stacked=stacked navbar=true}} {{#bs-nav-item}}{{#link-to "alert"}}Alert{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "button"}}Buttons{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "dropdown"}}Dropdown{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "forms"}}Forms{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "accordion"}}Accordion{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "collapse"}}Collapse{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "modal"}}Modals{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "progress"}}Progress bars{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "navs"}}Navs{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "navbars"}}Navbars{{/link-to}}{{/bs-nav-item}} {{/bs-nav}} {{/bs-collapse}} {{/bs-navbar}}
Plumbing
Plumbing
Plumbing
Plumbing
9
First Cut 😳{{#bs-navbar}} {{#bs-navbar-header}} {{!-- TODO: Create bs-navbar-toggle? --}} {{#bs-button toggle=true active=expanded action=toggle class="navbar-toggle collapsed"}} <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> {{/bs-button}} {{!-- TODO {{#bs-navbar-brand}}Brand{{/bs-navbar-brand}} --}} <a class="navbar-brand" href="#">Brand</a> {{/bs-navbar-header}} {{!-- TODO: Create bs-navbar-content instead of using bs-collapse --}} {{#bs-collapse collapse=collapsed class=(if expanded "collapse navbar-collapse in" "collapse navbar-collapse")}} {{#bs-nav type=type.id justified=justified stacked=stacked navbar=true}} {{#bs-nav-item}}{{#link-to "alert"}}Alert{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "button"}}Buttons{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "dropdown"}}Dropdown{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "forms"}}Forms{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "accordion"}}Accordion{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "collapse"}}Collapse{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "modal"}}Modals{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "progress"}}Progress bars{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "navs"}}Navs{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "navbars"}}Navbars{{/link-to}}{{/bs-nav-item}} {{/bs-nav}} {{/bs-collapse}} {{/bs-navbar}}
Plumbing
PlumbingMysteriousMysterious
MysteriousPlumbing
Plumbing
9
First Cut 😳{{#bs-navbar}} {{#bs-navbar-header}} {{!-- TODO: Create bs-navbar-toggle? --}} {{#bs-button toggle=true active=expanded action=toggle class="navbar-toggle collapsed"}} <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> {{/bs-button}} {{!-- TODO {{#bs-navbar-brand}}Brand{{/bs-navbar-brand}} --}} <a class="navbar-brand" href="#">Brand</a> {{/bs-navbar-header}} {{!-- TODO: Create bs-navbar-content instead of using bs-collapse --}} {{#bs-collapse collapse=collapsed class=(if expanded "collapse navbar-collapse in" "collapse navbar-collapse")}} {{#bs-nav type=type.id justified=justified stacked=stacked navbar=true}} {{#bs-nav-item}}{{#link-to "alert"}}Alert{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "button"}}Buttons{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "dropdown"}}Dropdown{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "forms"}}Forms{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "accordion"}}Accordion{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "collapse"}}Collapse{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "modal"}}Modals{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "progress"}}Progress bars{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "navs"}}Navs{{/link-to}}{{/bs-nav-item}} {{#bs-nav-item}}{{#link-to "navbars"}}Navbars{{/link-to}}{{/bs-nav-item}} {{/bs-nav}} {{/bs-collapse}} {{/bs-navbar}}
Plumbing
PlumbingMysteriousMysterious
MysteriousPlumbing
Plumbing
Typo
9
Issues
• Didn’t really work • Explicit class manipulation circumvented
transitions • Properties weren’t where I thought they were
10
First Really Working Version{{#bs-navbar as |toggleNavbar navbarCollapsed|}} {{#bs-navbar-header}} {{#bs-navbar-toggle action=toggleNavbar}} <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> {{/bs-navbar-toggle}} <a class="navbar-brand" href="#">Brand</a> {{/bs-navbar-header}} {{#bs-collapse collapsed=navbarCollapsed class=“navbar-collapse"}} ...
bs-navbar.hbs{{yield (action 'toggleNavbar') navbarCollapse}}
11
First Really Working Version{{#bs-navbar as |toggleNavbar navbarCollapsed|}} {{#bs-navbar-header}} {{#bs-navbar-toggle action=toggleNavbar}} <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> {{/bs-navbar-toggle}} <a class="navbar-brand" href="#">Brand</a> {{/bs-navbar-header}} {{#bs-collapse collapsed=navbarCollapsed class=“navbar-collapse"}} ...
Plumbing
Plumbing
Plumbing
bs-navbar.hbs{{yield (action 'toggleNavbar') navbarCollapse}}
11
Component Visibility
• A component’s properties are only visible in its template
• Component block params are visible in the template that is yielded to
• The same is true for action function references
12
Action Calling{{bs-navbar-toggle action="toggleNavbar"}}
By default it calls the action on the component then passes it to the controller if unhandled
13
Action Calling{{bs-navbar-toggle action="toggleNavbar"}}
By default it calls the action on the component then passes it to the controller if unhandled{{bs-navbar-toggle action=toggleNavbar}}
Without quotes, it can call an action function passed to it from a higher level, but …
13
Action Calling{{bs-navbar-toggle action="toggleNavbar"}}
By default it calls the action on the component then passes it to the controller if unhandled{{bs-navbar-toggle action=toggleNavbar}}
Without quotes, it can call an action function passed to it from a higher level, but …{{#bs-navbar as | toggleNavbar | }}
It must be exposed as a component block param or …
13
Action Calling{{bs-navbar-toggle action="toggleNavbar"}}
By default it calls the action on the component then passes it to the controller if unhandled{{bs-navbar-toggle action=toggleNavbar}}
Without quotes, it can call an action function passed to it from a higher level, but …{{#bs-navbar as | toggleNavbar | }}
It must be exposed as a component block param or …{{yield toggleNavbar=(action "toggleNavbar")}}
yielded through the template
13
Action Calling{{bs-navbar-toggle action="toggleNavbar"}}
By default it calls the action on the component then passes it to the controller if unhandled{{bs-navbar-toggle action=toggleNavbar}}
Without quotes, it can call an action function passed to it from a higher level, but …{{#bs-navbar as | toggleNavbar | }}
It must be exposed as a component block param or …{{yield toggleNavbar=(action "toggleNavbar")}}
yielded through the template
Prefer Closure Actions!
13
Improving Ergonomics{{#bs-navbar as |navbar|}} {{#bs-navbar-header}} {{#bs-navbar-toggle action=navbar.toggle}} <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> {{/bs-navbar-toggle}} <a class="navbar-brand" href="#">Brand</a> {{/bs-navbar-header}} {{#bs-collapse collapsed=navbar.collapsed class="navbar-collapse"}} ...
bs-navbar.hbs{{yield (hash collapsed=navbarCollapsed toggle=(action 'toggleNavbar'))}}
14
Can We Do Better?
• Why do these things need to be exposed at all?
• And while we’re at it, is there a better way to show that the various components are really more closely related?
15
Enter Contextual Components{{#bs-navbar as |navbar|}} {{#navbar.header}} {{#navbar.toggle}} <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> {{/navbar.toggle}} {{#navbar.brand}}Brand{{/navbar.brand}} {{/navbar.header}} {{#navbar.content}} {{#navbar.nav}} ...
16
The Templatebs-navbar.hbs{{yield (hash collapsed=collapsed
header=(component 'bs-navbar-header') toggle=(component ‘bs-navbar-toggle' action=(action 'toggleNavbar')) brand=(component 'bs-navbar-brand') content=(component ‘bs-navbar-content' collapsed=collapsed) nav=(component 'bs-navbar-nav') ) }}
17
Testing Contextual Components
• Integration testing • Do you test all of the components through the parent? • Do you just test the presence of the contextual
components? Easily done by referencing them. • How far should you go to verify the currying is done
properly? • To what extent should you test the aggregate
behavior? • Do you need an acceptance test?
18
Victory!
But at a cost This is an addon requiring compatibility to 1.13
Contextual components and hash were added in 2.3
😀😞
19
Final Form, But How?{{#bs-navbar}} <div class="navbar-header"> {{#bs-navbar-toggle}} <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> {{/bs-navbar-toggle}} <a class="navbar-brand" href="#">Brand</a> </div> {{#bs-navbar-content}} {{#bs-navbar-nav}}
...
20
Black Magic, Toggle Stylebs-navbar-toggle.jsimport Ember from 'ember';
import BsButtonComponent from 'ember-bootstrap/components/bs-button'; import NavbarComponent from 'ember-bootstrap/components/bs-navbar';
export default BsButtonComponent.extend({ ...
targetObject: Ember.computed(function() { return this.nearestOfType(NavbarComponent); }),
action: 'toggleNavbar',
actions: { toggleNavbar() { this.sendAction(); } } });
21
Black Magic, Toggle Stylebs-navbar-toggle.jsimport Ember from 'ember';
import BsButtonComponent from 'ember-bootstrap/components/bs-button'; import NavbarComponent from 'ember-bootstrap/components/bs-navbar';
export default BsButtonComponent.extend({ ...
targetObject: Ember.computed(function() { return this.nearestOfType(NavbarComponent); }),
action: 'toggleNavbar',
actions: { toggleNavbar() { this.sendAction(); } } });
21
Private!
Black Magic, Content Stylebs-navbar-content.jsimport Ember from 'ember';
import BsCollapseComponent from 'ember-bootstrap/components/bs-collapse'; import NavbarComponent from 'ember-bootstrap/components/bs-navbar';
export default BsCollapseComponent.extend({ navbar: Ember.computed(function() { return this.nearestOfType(NavbarComponent); }),
collapsed: Ember.computed.reads('navbar.collapsed') });
22
Black Magic, Content Stylebs-navbar-content.jsimport Ember from 'ember';
import BsCollapseComponent from 'ember-bootstrap/components/bs-collapse'; import NavbarComponent from 'ember-bootstrap/components/bs-navbar';
export default BsCollapseComponent.extend({ navbar: Ember.computed(function() { return this.nearestOfType(NavbarComponent); }),
collapsed: Ember.computed.reads('navbar.collapsed') });
22
Private!
Wrapping Up• Think about how it will be used and how
newcomers will perceive it
• Shoot for elegance, aesthetics, and ergonomics
• Use the latest cool features in the service of design and ergonomics, not for their own sake
• Remember not everyone’s on the cutting edge
23
Resources• Ember Guides for Components
• https://guides.emberjs.com/v2.7.0/components/passing-properties-to-a-component/
• https://guides.emberjs.com/v2.7.0/components/wrapping-content-in-a-component/
• https://guides.emberjs.com/v2.7.0/components/block-params/
• Eric Kelly’s (@HeroicEric) Boston Ember.js Talk
• https://speakerdeck.com/heroiceric/contextual-components
• https://youtu.be/Au3rHHuEZNI?t=1h6m42s
• Some Twiddles
• Component Property Scope: https://ember-twiddle.com/332c58bba5a2d0ac8874dd834e28ac06
• Action Calling: https://ember-twiddle.com/c9ba29e3c2f98937d4d0c8e493261a78
24
Contact Me
Stephen Vance http://www.vance.com
[email protected] @StephenRVance
srvance on GitHub and LinkedIn
25