EmberCamp 2016 – I Can Write My App With No Handlebars: Declarative Templating in Ember

EmberCamp 2016 – I Can Write My App With No Handlebars: Declarative Templating in Ember

Video: https://www.youtube.com/watch?v=ZpM4cV7rfj0&list=PL4eq2DPpyBbmrPasP06vK7cUkPUCNn_rW&index=3

In Ember, Handlebars is a small, Lisp-y language we use to express our application's user interface. We use Keywords, Helpers and Components and other primitives to build upon this language, and the result of this is a larger vocabulary in which we can declare our intent much more clearly. Let's explore how Keywords and Helpers augment Handlebars, and cover techniques and patterns for creating our own Helpers in good taste.

C8fccffc013096c4b465b50c284a5208?s=128

Lauren Tan

July 13, 2016
Tweet

Transcript

  1. 1.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    I CAN WRITE MY APP WITH NO HANDLEBARS Declarative Templating in Ember.js
  2. 2.
  3. 10.

    I CAN WRITE MY APP WITH NO HANDLEBARS EMBERCAMP 2016

    https://www.youtube.com/watch?v=_ahvzDzKdB0
  4. 12.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    “A language design can no longer be a thing. It must be a pattern – a pattern for growth – a pattern for growing the pattern for defining the patterns that programmers can use for their real work and their main goal.” –Guy Steele
  5. 15.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    {{get person propertyName}} {{t "user.edit.title"}} {{user-edit user=user}} {{if foo "bar" "baz"}}
  6. 16.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    {{#shopping-cart items=items as |cart|}} <ul> {{#each cart.items as |item|}} <li>{{item.name}} - {{item.quantity}} x {{item.price}}</li> {{/each}} </ul> <hr> {{cart.total}} <div class="button-group"> {{cart.checkout}} {{cart.empty}} </div> {{/shopping-cart}}
  7. 18.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    I. Templates are Data II. Your UI as a Language III.Helper Best Practices
  8. 19.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    I. Templates are Data II. Your UI as a Language III.Helper Best Practices
  9. 21.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    no logic extreme logic some logic
  10. 22.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    let Plates = require('plates'); let html = '<div id="test">Old Value</div>'; let data = { test: 'New Value }; let output = Plates.bind(html, data);
  11. 23.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    let age = 18; hbs`{{if (gt age 21) Adult Kid}}`;
  12. 24.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    return <ul> {this.props.posts.map((post) => { return <Item key={post.objectId} post={post}/> })} </ul>;
  13. 27.
  14. 28.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    export function gt([a, b]) { return a > b; } export default helper(gt);
  15. 29.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    AVOID PROGRAMMING IN THE TEMPLATE
  16. 30.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    {{#unless (or (and (gte value 0) (lt value 0.0001)) (and (lt value 0) (not allowNegativeResults)))}} ... {{/unless}}
  17. 31.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    export default Component.extend({ filteredItems: computed('items.@each.price', function() { // ... complicated logic }) });
  18. 32.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    <ul> {{#each (filter-by 'price' priceRange items) as |items|}} <li>{{item}}</li> {{/each}} </ul> https://github.com/DockYard/ember-composable-helpers#filter-by
  19. 35.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    “The "Rule of Least Power" suggests choosing the least powerful language suitable for a given purpose.”
  20. 36.
  21. 38.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    <ul> {{#each items as |item|}} <li>{{item}}</li> {{/each}} </ul>
  22. 39.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    <ul> {this.props.items.map((item) => { <li>{item}</li> })} </ul>;
  23. 40.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    <h1>Greetings!</h1> <p>Hello {{name}}, and welcome.</p>
  24. 41.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    <h1>Greetings!</h1> <p>"Hello " "Jim Bob" ", and welcome."</p>
  25. 42.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    el.childNodes; // ["Hello ", "Jim Bob", ", and welcome."]
  26. 45.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    action component concat debugger each each-in get hash if input link-to loc log mut outlet partial query-params render textarea unbound unless with
  27. 46.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    export function foo([model, value], { async }) { // .. } export default helper(foo); {{foo model "abc" async=true}}
  28. 47.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    I. Templates are Data II. Your UI as a Language III.Helper Best Practices
  29. 48.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    –Harold Abelson “Programs are meant to be read by humans and only incidentally for computers to execute.”
  30. 49.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    –Unknown “Every line of code is a liability. The code that's easiest to maintain is code that was never written.”
  31. 50.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    <html> <head> <title>Hello World</title> </head> <body> Hello, Example. Today's date and time is {{now}}. </body> </html>
  32. 51.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    page = Page(); head = new Head(); title = new Title(); title.setText("Hello World") head.add(title); page.add(head); body = new Body(); preamble = new StringBuffer(); preamble.append("Hello, Example. Today's date and time is "); preamble.append(Time.now().toString()); preamble.append("."); body.add(preamble.toString()); page.add(body);
  33. 53.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    no logic extreme logic some logic
  34. 54.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    {{#each filteredEmployees as |employee|}} ... {{/each}}
  35. 55.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    export default Component.extend({ employeeFilter: 'active', filteredEmployees: filter('employees', function() { let filter = get(this, 'employeeFilter'); return get(this, 'employees').filterBy('type', filter); }) });
  36. 56.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    {{#each-in groupedEmployees as |group employees|}} ... {{/each-in}}
  37. 57.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    export default Component.extend({ employeeGroupBy: 'role', filteredEmployees: /* ... */, groupedEmployees: computed('employeeGroupBy', 'filteredEmployees', function() { // ... complicated logic }) });
  38. 58.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    https://github.com/DockYard/ember-composable-helpers {{#with (filter-by "type" employeeFilter employees) as |filteredEmployees|}} {{#each-in (group-by employeeGroupBy filteredEmployees) as |group employees|}} ... {{/each-in}} {{/with}}
  39. 59.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    https://github.com/DockYard/ember-composable-helpers {{#each-in (group-by employeeGroupBy (filter-by "type" employeeFilter employees)) as |group employees|}} ... {{/each-in}}
  40. 62.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    no logic extreme logic some logic
  41. 64.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    {{#form-for person as |f|}} {{f.text-field "firstName"}} {{f.text-field "lastName"}} {{f.date-field "birthDate"}} {{f.submit "Save"}} {{/form-for}} https://github.com/martndemus/ember-form-for
  42. 65.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    I. Templates are Data II. Your UI as a Language III.Helper Best Practices
  43. 67.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    –Jiro Ono “In order to make delicious food, you must eat delicious food. […] Without good taste, you can’t make good food.”
  44. 71.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    {{capitalize "hello"}} {{capitalize "hello"}}
  45. 72.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    export function add([a, b]) { return a + b; } export default helper(add);
  46. 74.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    let x = 0; export function add([a]) { return x += a; } export default helper(add);
  47. 75.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    const sortMap = { ascending(a, b) { return a - b; }, descending(a, b) { return b - a; } }; export function sort([order, items]) { let sortFn = sortMap[order] || K; items.sort(sortFn); return items; } export default helper(append);
  48. 77.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    Numbers: {{numbers}} <!-- 2, 5, 4, 1, 3 --> Sorted: {{sort "ascending" numbers}} <!-- 1, 2, 3, 4, 5 --> Numbers: {{numbers}} <!-- 1, 2, 3, 4, 5 --> https://ember-twiddle.com/717d4c5f774d0b2822e853d67ef7c3a9?openFiles=templates.application.hbs%2C
  49. 79.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    EMBER-CHANGESET https://github.com/DockYard/ember-changeset
  50. 81.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    export function changeset(obj, validateFn, validationMap) { return EmberObject.extend({ // changeset implementation }); } export default class Changeset { constructor() { return changeset(...arguments).create(); } }
  51. 82.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    export function changeset([model, validationMap]) { if (isObject(validationMap)) { return new Changeset(model, lookupValidator(validationMap), validationMap); } return new Changeset(model, validationMap); } export default helper(changeset);
  52. 83.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    {{dummy-form changeset=(changeset user EmployeeValidations) submit=(action "submit") rollback=(action "rollback") }} https://bit.ly/ember-changeset-demo
  53. 86.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    export function join([separator, ...words]) { return words.join(separator); } export default helper(join); export function sum([...values]) { return values.reduce((acc, curr) => { return acc + curr; }, 0); } export default helper(join); export function gt([a, b]) { return a > b; } export default helper(gt); {{join "-" "hello" "world"}} <!-- hello-world --> {{sum 1 2 3}} <!-- 6 --> {{gt 1 2}} <!-- false -->
  54. 88.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    export function repeat([length, value]) { if (typeOf(length) !== 'number') { return [value]; } return Array.apply(null, { length }).map(() => value); } export default helper(repeat); https://github.com/DockYard/ember-composable-helpers#repeat
  55. 89.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    {{#each (repeat 3 "Developers") as |text|}} {{text}} {{/each}} <!-- Developers Developers Developers -->
  56. 91.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    {{#with (changeset user userValidations) as |changeset|}} {{input value=changeset.firstName}} <button type="submit" {{action "save" changeset}}>Save</button> {{/with}}
  57. 92.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    {{#with (hash bar=(component "hello-world")) as |foo|}} {{foo.bar name="Jim Bob"}} {{/with}} <!-- Hello world, Jim Bob! -->
  58. 93.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    {{complex-component ui=(hash map=(component "user-map") sidebar=(component "user-sidebar") form=(component "user-form")) }}
  59. 94.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    {{ui.sidebar user=user}} {{ui.map lat=user.lat lng=user.lng}} {{ui.form user=user}}
  60. 95.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    export function registerDummyComponent(context, name = 'dummy-component', opts = {}) { let owner = getOwner(context); let options = assign({ tagName: 'dummy' }, opts); let DummyComponent = Component.extend(options); unregisterDummyComponent(context, name); owner.register(`component:${name}`, DummyComponent); } export function unregisterDummyComponent(context, name = 'dummy-component') { let owner = getOwner(context); if (owner.resolveRegistration(`component:${name}`)) { owner.unregister(`component:${name}`); } }
  61. 96.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    test('it does something', function(assert) { registerDummyComponent(this); this.set('user', { firstName: 'Jim', lat: 51.5074, lng: 0.1278 }); this.on('submit', K); this.render(hbs` {{complex-component ui=(hash map=(component "dummy-component") sidebar=(component "dummy-component") form=(component "dummy-component")) }} `); assert.ok(...); });
  62. 98.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    export function toggle([obj, prop]) { return function() { set(obj, prop, !get(obj, prop)); }; } https://github.com/DockYard/ember-composable-helpers#toggle
  63. 99.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    <button {{action (toggle "isExpanded" this)}}> {{if isExpanded "I am expanded" "I am not"}} </button>
  64. 100.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    <button {{action (pipe addToCart purchase redirectToThankYouPage) item}}> 1-Click Buy </button> https://github.com/DockYard/ember-composable-helpers#pipe
  65. 101.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    export function pipe(actions = []) { return function(...args) { return actions.reduce((acc, curr, idx) => { if (idx === 0) { return curr(...args); } return invokeFunction(acc, curr); }, undefined); }; } export default helper(pipe);
  66. 104.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    export default Helper.extend({ localesService: inject.service('locales'), currentLocale: readOnly('localesService.currentLocale'), compute([key]) { let currentLocale = get(this, 'currentLocale'); return get(this, 'localesService').lookup(currentLocale, key); }, localeDidChange: observer('currentLocale', function() { this.recompute(); }) });
  67. 108.

    I CAN WRITE MY APP WITH NO HANDLEBARS EMBERCAMP 2016

    export default Component.extend({ @computed('payment', 'rate', 'periods') annuity(payment, rate, periods) { let factor = ((1 - Math.pow(1 + rate, -periods)) / rate); return payment * factor; } });
  68. 110.

    I CAN WRITE MY APP WITH NO HANDLEBARS EMBERCAMP 2016

    {{#each (repeat 3) as |nil index|}} <div data-thing={{concat "foo-" index}}> {{!some HTML block}} </div> {{/each}} https://github.com/DockYard/ember-composable-helpers#repeat
  69. 111.

    I CAN WRITE MY APP WITH NO HANDLEBARS EMBERCAMP 2016

    {{#unless (or (and (gte value 0) (lt value 0.0001)) (and (lt value 0) (not allowNegativeResults)))}} ... {{/unless}}
  70. 113.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    I. Templates are Data II. Your UI as a Language III.Helper Best Practices
  71. 114.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    I. Templates are Data II. Your UI as a Language III.Helper Best Practices
  72. 115.

    EMBERCAMP 2016 I CAN WRITE MY APP WITH NO HANDLEBARS

    I. Templates are Data II. Your UI as a Language III.Helper Best Practices
  73. 116.
  74. 117.

    I CAN WRITE MY APP WITH NO HANDLEBARS EMBERCAMP 2016

    LAUREN TAN SUGARPIRATE_ POTETO Thanks {{name}}!