Chicago Ember.js meetup 9/5



Chicago Ember.js meetup 9/5

0 0


component-talk

Slideshow for Ember.js Chicago meetup 9/5

On Github stevekane / component-talk

Chicago Ember.js meetup 9/5

Special Thanks to:

hosted by Mantra, Inc. and Lightbank, Inc.

About Me @stv_kn

Javascript developer living in Bucktown and currently working for Mantra, Inc building MVPs for new startups.

Passions

  • Snowboarding
  • Programming
  • Philosophy
  • Boats

Angry Ball of Mud

Becky, does my controller look fat to you?

Architecture is about intent, not your framework. -Bob Martin Architecture, the Lost Years
The boundaries of your program ARE your program in a very real sense. -Gary Bernhardt Destroy All Software
My test doesn't know, doesn't wanna know. It doesn't care, doesn't wanna care. We're just programming to the interface. -Mark Trostler Testable Javascript
Divide and conquer. -Phillip II, King of Macedon
Be the ball Danny. Be the ball. -Ty Webb (fictional philosopher) Caddy Shack

SRP (ya, you know me)

Every class should have a single responsibility and that responsibility should be entirely encapsulated by the class.

Liskov Substitution Principle

Classes that implement the same interface may swap out implementations without breaking the $(!&!#! out of your entire program (paraphrased)

Interface-Oriented Programming

Programs are composed of interfaces and the implementation details of the system behind that interface should be irrelevant to the user (paraphrased)

Composition

Composable systems provide recombinant components that can be selected and assembled in combinations to satisfy variable requirements.

Ember.Component

Subclass of Ember.View that ONLY acts on data explicitly passed into it and exposes a strict and well-defined interface for interaction.
  //handlebars template
  {{secondsViewed}}
  //javascript code
  App.OurTimetrackerComponent = Ember.Component.extend({

    msViewed: 0,
    oldTime: null,
    newTime: null,

    secondsViewed: function () {
      var msViewed = this.get('msViewed');
      return Math.round(msViewed);
    },

    startTimer: function () {
      Ember.run.later(this, function () {
        var newTime = Date.now()
          , oldTime = this.get('oldTime') || Date.now()
          , detaMs = newTime - oldTime;

        this.incrementProperty('msViewed', deltaMs);
        this.set('oldtime', newtime);
      }, 500);
    }.on('didInsertElement')
  });

The inside is so messy...

Let's see what it's like to use this class in ember..

  {{! display the time a user has spent on this view }}

  {{our-timetracker}}

Winning

  • declarative configuration
  • single responsibility
  • narrow API
  • reusable

a fancy counter...so what

let's add some feedback

  {{our-timetracker action="logTimeStamp"}}

Modify our code to use sendAction

//snippet from our origin timetracker
  startTimer: function () {
    Ember.run.later(this, function () {
      var newTime = Date.now()
        , oldTime = this.get('oldTime') || Date.now()
        , deltaMs = newTime - oldTime;

      this.incrementProperty('msViewed', deltaMs);
      //new
      this.sendAction("action", this.get('msViewed'));
      //endnew
      this.set('oldTime', newTime);
    }, 500);
  }.on('didInsertElement')

What to do with this data?

localStorage?

  {{our-timetracker action="logTimeStamp"}}
  App.SomeController = Ember.Controller.extend({
    actions: {
      logTimeStamp: function (ms) {
        var lsTimeStamps=localStorage['timestamps'] || "[]"
          , timeStamps=JSON.parse(lsTimeStamps)
          , stringifiedTimeStamps;

        timeStamps.push(ms);
        stringifiedTimeStamps=JSON.stringify(timeStamps);
        localStorage['timestamps']=stringifiedTimeStamps);
      }     
    }
  });  

Websocket?!

  App.SomeController = Ember.Controller.extend({
    actions: {
      logTimeStamp: function (ms) {
        this.websocket.send(ms);
      }     
    }
  });  

D3??!!

  App.TimeGraphComponent = Ember.Component.extend({
    actions: {
      logTimeStamp: function (ms) {
        //weird d3 code belongs here
      }     
    }
  });  
@heyjinkim's blogpost w/ real implementation

Can this counter drive behavior elsewhere?

Change the template

  {{! some.handlebars }}
  {{! template for somecontroller and someview }}

  {{our-timetracker action="logTimeStamp" msViewed=view.msOnPage}}
  {{our-timegraph data=view.msOnPage}}

Change the javascript...

  App.SomeView = Ember.View.extend({
    msOnPage: 0 
  });

...How does this work?

Bound properties

  {{our-timetracker action="logTimeStamp" msViewed=view.msOnPage}}

changes to bound properties are propagated to every part of your app that depends on them...for free.

Advanced communication - bus

  {{our-timetracker outputBus=timeBus}}
  {{our-timegraph inputBus=timeBus}}
  App.SomeController = Ember.Controller.extend({
    //not an ember builtin!
    timeBus: App.Bus.create()
  });

  App.TimetrackerComponent = Ember.Component.extend({
    //psuedocode for brevity
    onTick (ms): function () {
      var outputBus = this.get('outputBus');
      outputBus.push({
        timeStamp: ms,
        otherData: //something
      });
    }
  });

  App.TimegraphComponent = Ember.Component.extend({
    didInsertElement: function () {
      var inputBus = this.get('inputBus');
      inputBus.on('data', function (data) {
        this.graph(data.timeStamp);
      });
    }
  });

Breathe

Questions?

Now the fun part...

Let's break down a user interface

Disclaimer: This section is focused on process over implementation

List the data models

  • City
  • DateTime
  • Hourly Weather Data
    • Temperature
    • Precipitation
    • Humidity
    • Wind speed
    • Wind direction
  //data is a blob of weather data fetched by the Route
  App.WeatherController = Ember.Controller.extend({

    cityName: "Chicago",

    activeDateTime: Date.now(),

    activeDaysData: function () {
      var activeDateTime = this.get('activeDateTime')
        , data = this.get('data');
      this.get('data').filterProperty('date', activeDateTime);
    }.property('activeDateTime', 'data'),

    isMetric: true,
    }
  });

Group your UI by data, purpose, location

Weather Overview Component

Computes current temperature

Displays City Name, Current Date and Time, Summary

  {{weather-overview
    cityName=cityName
    dateTime=activeDateTime
    data=activeDaysData}}

Weather Daily Averages Component

Computes and displays average windspeed, humidity, precipitation

  {{weather-daily-averages data=activeDaysData isMetric=isMetric}}

Weather Graphical Overview Component

Computes current temperature and activeDays overview

Displays temperature, and graphic based on overview

Toggles "isMetric" value via clicks

  {{weather-graphical-overview
    data=activeDaysData
    dateTime=activeDateTime
    isMetric=isMetric}}

Sometimes, further decomposition is desireable

Weather Graph Wrapper

Has an internal state to track what is being displayed

Contains "controls" component and "graph component"

  {{weather-graph-wrapper data=activeDaysData isMetric=isMetric}}

Weather Graph

Computes/displays range of a type of data

State change will cause graph to render new Data

  {{weather-graph data=activeDaysData state=state isMetric=isMetric}}
We could further enhance this component by passing in a range of hours, a scale, or even display properties

Weather Graph Controls

Changes "state" based on clickable buttons

NOTE: this is really just a set of "tabs" and could be further generalized

  {{weather-graph-controls state=state}}

Benefits of this arrangement

Graph component may be used elsewhere and have its state driven by other means

Controls component may be used to drive some other state, perhaps tabs?

Extrapolation

Create a "daily overview" component

Create a "weekly overview list" component that USES the "daily overview" component in its template

  {{! weather-weekly-overview template }}

  {{#each day in days}}
    {{weather-daily-overview data=day.data}}
  {{/each}}

Clicking a "daily overview" will affect the activeDateTime

Benefit of this arrangement

Daily overview component may be used elsewhere

Weekly overview is concerned with determining list of daily data

Daily overview is concerned with computing/displaying daily data

Putting it all together

Let's group the first three components into "overview wrapper" component as we have done in with the previous arrangements

  {{! weather.handlebars }}

  {{weather-overview-wrapper
    cityName=cityName
    dateTime=activeDateTime
    data=activeDaysData
    isMetric=isMetric}}

  {{weather-graph-wrapper
    data=activeDaysData
    isMetric=isMetric}}

  {{weather-weekly-overview
    data=data
    dateTime=activeDateTime
    isMetric=isMetric}}

Final thoughts

  • Composable systems are easy to reason about
  • Components promote clean interfaces
  • Components are easy to test
  • Data can be mapped to functionality w/o implementation
  • Fully consistent with the upcoming WebComponents API