EmberJS Designing Composable Components

EmberJS Designing Composable Components

Slides from the July 2014 Ember SF talk "Designing Composable Components"

9035acc6a3970f0a52f66ed3f703219b?s=128

Ben Lesh

July 22, 2014
Tweet

Transcript

  1. Designing  Composable   Components  

  2. Who  am  I?   Ben  Lesh   Twi8er:  @benlesh  

    ben@benlesh.com         Lucky  enough  to  work  here:    
  3. What  is  a  “component”  really?  

  4. What  is  a  “component”  really?   •  It’s  this  thing

     that  I  just  know  I’m  going  to  use   over  and  over  again  in  my  applicaGon!  
  5. What  is  a  “component”  really?   •  It’s  this  thing

     that  I  just  know  I’m  going  to  use   over  and  over  again  in  my  applicaGon!   •  Okay,  so  maybe  it’s  a  li8le  different   everywhere  I  need  it.  But  hey,  I’m  saving   myself  some  typing…  
  6. What  is  a  “component”  really?   •  It’s  this  thing

     that  I  just  know  I’m  going  to  use   over  and  over  again  in  my  applicaGon!   •  Okay,  so  maybe  it’s  a  li8le  different   everywhere  I  need  it.  But  hey,  I’m  saving   myself  some  typing…   •  Ugh,  I  hate  maintaining  this  thing,  but  at  least   in  90%  of  my  use  cases  I  can  just  drop  it  in   place  and  it  will  work…  I  hope.  
  7. A  “simple”  start…   I  need  to  put  arrays  of

     data  in  a  table  in  my  view   a  lot.  And  I  need  to  be  able  to  click  the  header   and  sort  them…     Hey!  I  can  make  a  component  for  that!  
  8. A  “simple”  start…   {{data-­‐table  rows=myData}}  

  9. A  “simple”  start…   <table>      <thead>    

         <tr>          {{#each  column  in  columns}}              <td  {{action  ‘sortBy’  column}}>{{column.name}}</td>          {{/each}}          </tr>      </thead>        <tbody>          {{#each  row  in  computedRows}}              <tr>                  {{#each  cell  in  row.cells}}                      <td>{{cell.value}}</td>                  {{/each}}              </tr>          {{/each}}      </tbody>   </table>  
  10. A  wild  requirement  appears!!   Um…  yeah,  I’m  going  to

     need  the  third  column  on  that  one  page  to   be  an  image.  If  you  could  do  that  that  would  be  great,  thanks…  
  11. No  sweat…   <table>      <thead>      

       <tr>          {{#each  column  in  columns}}              <td  {{action  ‘sortBy’  column}}>{{column.name}}</td>          {{/each}}          </tr>      </thead>        <tbody>          {{#each  row  in  computedRows}}              <tr>                  {{#each  cell  in  row.cells}}                      <td>                      {{#if  cell.isImage}}                          <img  {{bind-­‐attr  src=cell.value}}/>                      {{else}}                          {{cell.value}}                      {{/if}}                      </td>                  {{/each}}              </tr>          {{/each}}      </tbody>   </table>  
  12. …  and  another  one.   Hold  on,  some%mes  if  it’s

     an  image  it  will  have  a   link.  But  not  all  the  Gme,  okay?  Yeah.  Great…  
  13. Okay…   <table>      <thead>        

     <tr>          {{#each  column  in  columns}}              <td  {{action  ‘sortBy’  column}}>{{column.name}}</td>          {{/each}}          </tr>      </thead>        <tbody>          {{#each  row  in  computedRows}}              <tr>                  {{#each  cell  in  row.cells}}                      <td>                      {{#if  cell.isImage}}                          {{#if  cell.isLink}}                              <a  {{bind-­‐attr  href=cell.url}}><img  {{bind-­‐attr  src=cell.value}}/></a>                          {{else}}                              <img  {{bind-­‐attr  src=cell.value}}/>                          {{/if}}                      {{else}}                          {{cell.value}}                      {{/if}}                      </td>                  {{/each}}              </tr>          {{/each}}      </tbody>   </table>  
  14. This  isn’t  going  to  stop   “I  need  a  radio

     bu8on  someGmes,  and  a  whole  form  other  Gmes,  and  here  I  need  a…”  
  15. So  what  to  do?   •  This  is  clearly  turning

     into  a  mess.  
  16. So  what  to  do?   •  This  is  clearly  turning

     into  a  mess.   •  Perhaps  it  shouldn’t  have  been  a  component   at  all?  
  17. So  what  to  do?   •  This  is  clearly  turning

     into  a  mess.   •  Perhaps  it  shouldn’t  have  been  a  component   at  all?   •  Well,  no,  I  did  get  some  good  out  of  it.  I  sGll   need  a  sortable  table.  
  18. Composable  Components   “Composability  is  a  system  design  principle  that

      deals  with  the  inter-­‐relaGonships  of   components.  A  highly  composable  system   provides  recombinant  components  that  can  be   selected  and  assembled  in  various  combinaGons   to  saGsfy  specific  user  requirements.”     -­‐  Wikipedia  (the  source  of  all  truth  EVER)  
  19. Composable  Components   TL;DR:  Make  a  bunch  of  components  that

     work   together  in  a  flexible  way.  
  20. What  would  that  look  like?   {{#my-­‐table  rows=myData}}    

     {{#my-­‐column  sortField=“foo”}}          {{#my-­‐column-­‐header}}              Foo          {{/my-­‐column-­‐header}}          {{#my-­‐column-­‐cell}}              {{row.foo}}          {{/my-­‐column-­‐cell}}      {{/my-­‐column}}        {{#my-­‐column  sortField=“imageName”}}          {{#my-­‐column-­‐header}}              Image          {{/my-­‐column-­‐header}}          {{#my-­‐column-­‐cell}}              <a  {{bind-­‐attr  href=row.url}}>                  <img  {{bind-­‐attr  src=row.imageSrc  alt=row.imageName}}/>              </a>          {{/my-­‐column-­‐cell}}      {{/my-­‐column}}   {{/my-­‐table}}  
  21. What  would  that  look  like?   {{#my-­‐table  rows=myData}}    

     {{#my-­‐column  sortField=“foo”}}          {{#my-­‐column-­‐header}}              Foo          {{/my-­‐column-­‐header}}          {{#my-­‐column-­‐cell}}              {{row.foo}}          {{/my-­‐column-­‐cell}}      {{/my-­‐column}}        {{#my-­‐column  sortField=“imageName”}}          {{#my-­‐column-­‐header}}              Image          {{/my-­‐column-­‐header}}          {{#my-­‐column-­‐cell}}              <a  {{bind-­‐attr  href=row.url}}>                  <img  {{bind-­‐attr  src=row.imageSrc  alt=row.imageName}}/>              </a>          {{/my-­‐column-­‐cell}}      {{/my-­‐column}}   {{/my-­‐table}}   A  container  component       •  Manages  columns   •  Manages  data  (sorGng/etc)   •  Handles  rendering  of  HTML  table  
  22. What  would  that  look  like?   {{#my-­‐table  rows=myData}}    

     {{#my-­‐column  sortField=“foo”}}          {{#my-­‐column-­‐header}}              Foo          {{/my-­‐column-­‐header}}          {{#my-­‐column-­‐cell}}              {{row.foo}}          {{/my-­‐column-­‐cell}}      {{/my-­‐column}}        {{#my-­‐column  sortField=“imageName”}}          {{#my-­‐column-­‐header}}              Image          {{/my-­‐column-­‐header}}          {{#my-­‐column-­‐cell}}              <a  {{bind-­‐attr  href=row.url}}>                  <img  {{bind-­‐attr  src=row.imageSrc  alt=row.imageName}}/>              </a>          {{/my-­‐column-­‐cell}}      {{/my-­‐column}}   {{/my-­‐table}}   Column  definiGons     •  Register  columns  with  the   container   •  PosiGon  determines  display   ordering   •  Contains  metadata  about  the   column  (like  what  to  sort  by   when  clicked)  
  23. What  would  that  look  like?   {{#my-­‐table  rows=myData}}    

     {{#my-­‐column  sortField=“foo”}}          {{#my-­‐column-­‐header}}              Foo          {{/my-­‐column-­‐header}}          {{#my-­‐column-­‐cell}}              {{row.foo}}          {{/my-­‐column-­‐cell}}      {{/my-­‐column}}        {{#my-­‐column  sortField=“imageName”}}          {{#my-­‐column-­‐header}}              Image          {{/my-­‐column-­‐header}}          {{#my-­‐column-­‐cell}}              <a  {{bind-­‐attr  href=row.url}}>                  <img  {{bind-­‐attr  src=row.imageSrc  alt=row.imageName}}/>              </a>          {{/my-­‐column-­‐cell}}      {{/my-­‐column}}   {{/my-­‐table}}   “Template”  components     •  Capture  templates  to  render  in   headers  and  cells  for  each   column  
  24. What  would  that  look  like?   {{#my-­‐table  rows=myData}}    

     {{#my-­‐column  sortField=“foo”}}          {{#my-­‐column-­‐header}}              Foo          {{/my-­‐column-­‐header}}          {{#my-­‐column-­‐cell}}              {{row.foo}}          {{/my-­‐column-­‐cell}}      {{/my-­‐column}}        {{#my-­‐column  sortField=“imageName”}}          {{#my-­‐column-­‐header}}              Image          {{/my-­‐column-­‐header}}          {{#my-­‐column-­‐cell}}              <a  {{bind-­‐attr  href=row.url}}>                  <img  {{bind-­‐attr  src=row.imageSrc  alt=row.imageName}}/>              </a>          {{/my-­‐column-­‐cell}}      {{/my-­‐column}}   {{/my-­‐table}}   User  defined  templates     •  Can  be  anything  the  developer   needs  to  be.   •  Has  things  like  current  {{row}}  or   {{column}}  injected  into  it’s   context.   •  SGll  handles  outer  context  and   controller  acGons!  
  25. The  container:   establishing  a  connecGon   •  Add  register

     method  to  allow  children  to   register  themselves.   •  Add  “isSomeComponent”  property  (can  be   named  anything)  to  help  child  components   find  a  reference  to  the  parent.   •  Add  unregister  method!   •  Because  we’re  pulling  templates  from  child   components,  we  don’t  want  to  render  unGl   those  components  have  been  inserted.  
  26. The  container:   establishing  a  connecGon   export  default  Ember.Component.extend({

         isTable:  true,        hasRendered:  false,        columns:  function()  {          return  [];      }.property(),        registerColumn:  function(column)  {          this.get(‘columns’).pushObject(column);      },        unregisterColumn:  function(column)  {          this.get(‘columns’).removeObject(column);      },        _notifyRendered:  function(){          this.set(‘hasRendered’,  true);      }.on(‘didInsertElement’),   });  
  27. The  child:   establishing  a  connecGon   •  Locate  the

     parent  with   this.nearestWithProperty  on  init.   •  Be  sure  to  unregister  on  willDestroyElement!  
  28. The  child:     establishing  a  connecGon   export  default

     Ember.Component.extend({      isColumn:  true,        _registerWithTable:  function(){          var  table  =  this.nearestWithProperty(‘isTable’);          table.registerColumn(this);          this.set(‘table’,  table);      }.on(‘init’),        _unregister:  function(){          this.get(‘table’).unregisterColumn(this);      }.on(‘willDestroyElement’),   });  
  29. The  template  components     •  These  are  serving  up

     templates,  we  don’t   want  them  to  render,  so  override   renderToBuffer.   •  We  do  need  to  noGfy  the  parent  column  that   the  child  exists.   •  Code  for  both  the  header  and  cell   components  will  be  basically  the  same.  
  30. The  template  components:     establishing  a  connecGon   export

     default  Ember.Component.extend({      _registerWithColumn:  function(){          var  column  =  this.nearestWithProperty(‘isColumn’);          column.set(‘header’,  this);      }.on(‘init’),        renderToBuffer:  function()  {          //  Override:  don’t  render.      },   });  
  31. The  container:   rendering  the  HTML   •  Use  views

     to  render  the  passed  templates  by   sehng  the  template  a8ribute  on  the  view.   •  You  must  use  {{yield}}  to  force  the  child   components  to  be  processed  at  all.  
  32. The  container:  rendering  the  HTML   {{#if  hasRendered}}    

     <thead>          <tr>              {{#each  column  in  columns}}                  <th>{{view  Ember.View  template=column.header.template}}</th>              {{/each}}          </tr>      </thead>      <tbody>          {{#each  row  in  rows}}              <tr>                  {{#each  column  in  columns}}                      {{view  Ember.View  tagName=“td”                              template=column.cell.template}}                  {{/each}}              </tr>          {{/each}}      </tbody>   {{/if}}     {{yield}}  
  33. One  problem  lei…   Currently,  the  templates  in  our  column

     cells  and   headers  can’t  see  the  properGes  and  acGons  on   our  view  controller!     {{#my-­‐table-­‐cell}}      <a  {{action  ‘wee’}}>This  doesn’t  work</a>      <p>WTH?  “{{propFromController}}”  is  blank!</p>   {{/my-­‐table-­‐cell}}  
  34. SoluGon:  templateData.view.controller   •  In  our  container  component,  create  an

     aliased   property  for  `templateData.view.controller`.   •  In  our  container  .hbs  set  each  Ember.View’s   controller  property  to  this  parentController.  
  35. SoluGon:  templateData.view.controller   parentController:          Ember.computed.alias(‘templateData.view.controller’),  

  36. SoluGon:  templateData.view.controller   {{#if  hasRendered}}      <thead>    

         <tr>              {{#each  column  in  columns}}                  <th>{{view  Ember.View  controller=parentController                                template=column.header.template}}</th>              {{/each}}          </tr>      </thead>      <tbody>          {{#each  row  in  rows}}              <tr>                  {{#each  column  in  columns}}                      {{view  Ember.View  tagName=“td”  controller=parentController                              template=column.cell.template}}                  {{/each}}              </tr>          {{/each}}      </tbody>   {{/if}}     {{yield}}  
  37. (Hopefully  here  I’ll  show     the  example  project)  

  38. How  we  use  this  at  Neklix   •  Tables  (similar

     to  what  you’ve  seen)   •  Line  graphs   •  Bar  graphs   •  Area  graphs   •  Mixed  area/bar/line  graphs   •  Grouped  tables.   •  Inline  data  visualizaGons.  
  39. A  graph  component  might  look  like     {{#nf-­‐graph  width=300

     height=100}}      {{#nf-­‐x-­‐axis  height=30}}          <text>{{tick.value}}</text>      {{/nf-­‐x-­‐axis}}        {{#nf-­‐y-­‐axis}}          <text>{{tick.value}}</text>      {{/nf-­‐y-­‐axis}}        {{#nf-­‐graph-­‐content}}          {{nf-­‐line  data=lineData  xprop=“x”  yprop=“y”}}      {{/nf-­‐graph-­‐content}}   {{/nf-­‐graph}}  
  40. ~  The  End  ~   Ben  Lesh   Twi8er:  @benlesh

      Email:  ben@benlesh.com     Example  project:   h8p://github.com/blesh/ember-­‐composable-­‐ components-­‐example     Neklix  data  visualizaGon  components  hopefully   part  of  NeklixOSS  soon!