Supercharge your productivity with Ember.js



Supercharge your productivity with Ember.js

0 0


ember_supercharge


On Github jagthedrummer / ember_supercharge

Supercharge your productivity with Ember.js

Show of hands

  • Who has experience with Ember?
  • Who has experience with single page apps?
  • Who has experience with Rails or .NET MVC?

Where does the faceof your web app live?

Where is it generated?

"Traditional" web apps

Generate a giant string of HTML on the server Deliver a fully formed 'document' to the browser Apply a sprinkle of JavaScript Replace bits of markup via AJAX

Single page apps

Deliver empty application assets to the browser Pull/push raw data via JSON Assemble the 'document' in the browser

Challenges of SPAs

Challenges of SPAs

  • Rebuilding the a tag
  • Data retrieval & display
  • User interaction
  • Data synching
  • Manipulating the URL
  • Setting up initial state

Ember to the rescue!

Ember to the rescue!

Ember strives to solve all of these problems

in an opinionated way

MVC-ish

NOT server side MVC

Many of the same termsbut sometimes they mean different things

MVC+

  • Model
  • View
  • Controller
Router Route Template

Over Simplified Request Path

Router
↓Route
↓Model
↓Controller
↓View
↓Template

Comparison with Rails - Similarities

Ember <=> Rails

Router <=> config/routes.rb

Connect URLs to code paths

Model <=> Model

Object wrapper for business data

Templates <=> Templates

Markup for UI

Comparison with Rails - Differences

Ember <=> Rails

Route <=> GET Controller actions (index, new, edit)

Setup data for user interaction

Controller <=> Other Controller actions (create, etc...)

Handle user interactions and input

Views <=> jQuery Sprinkle

Interaction & DOM Logic

WAT?!?

Context Matters!

Different needscall for different solutions

Rails is stateless

Ember apps are stateful

Rails has an explicitrequest/response cycle

Ember apps are long runningwith internal state transitions

A bit about me

Jeremy Green

Consultant (I'm available, call me.)

Also into drumming, photography, and brewing

Organizer of OkcRuby.org

@jagthedrummer jeremy@octolabs.com http://www.octolabs.com/ These items are in chronological order. I've been drumming much more than half my life. Coding, just about half. * next fragment * Thanks to Jesse and Vance of okcjs for organizing Thunder Plains. * next fragment * I'm historically bad at Twitter, but trying to get better. Contacting me through OctoLabs is more reliable.

PatternLab Demo

PatternLab Demo

http://emberjs.jsbin.com/falat/8/edit?output

The Plan

The Plan

State a Goal Rails way (sometimes) Ember way GOTO 1

Getting Started

Getting Started

Starting With A Very Simple App

Rails

  # config/routes.rb
  Rails.application.routes.draw do
    root :to => 'static_pages#home'
  end
  
  <!-- app/views/static_pages/home.html.erb -->
  <h1>This app is useless!</h1>
  
  # app/controllers/static_pages_controller.rb
  class StaticPagesController < ApplicationController
  end
  

Ember

  App = Ember.Application.create();
  
  <script type="text/x-handlebars">
    <h2>Welcome to the Pattern Lab!</h2>
    <p>Your home for procedurally generated patterns.</p>
    <p>This is a small demo app built with Ember and Ember Data.</p>
  </script>
  
http://emberjs.jsbin.com/guyed/1/edit

Ember "magic" at work.

Only write the parts you need.

Ember will 'auto generate' any parts of the stack that you don't write

If you wanted to be explicit

  App = Ember.Application.create();

  App.Router.map(function(){
    this.route('index',{ path:'' })
  });
  
  App.IndexRoute = Ember.Route.extend({});

  App.IndexController = Ember.ObjectController.extend({});

  App.IndexView = Ember.View.extend({});

  <script type="text/x-handlebars" id="index">
    <h1>This app is verbose AND useless!</h1>
  </script>
  
http://emberjs.jsbin.com/boyim/3/edit

Don't write it if you don't need it!

Application layout

Rails

  <!--app/views/layouts/application.html.erb -->
  <html>
    <head>...</head>
    <body>

      <h1>This is the layout</h1>
      <hr>
      <%= yield %>

    </body>
  </html>
  
  <!-- app/views/static_pages/home.html.erb -->
  <h1>This app is useless!</h1>
  

Ember

  <script type="text/x-handlebars">
    <div class="header">
      <h1 id='title'>
        {{#link-to 'index'}}PatternLab{{/link-to}}
      </h1>
    </div>

    {{outlet}}
  </script>
 

  <script type="text/x-handlebars" id="index">
    <h2>Welcome to the Pattern Lab!</h2>
    <p>Your home for procedurally generated patterns.</p>
    <p>This is a small demo app built with Ember and Ember Data.</p>
  </script>
  
http://emberjs.jsbin.com/rumit/1/edit

Adding a page

Rails

  # config/routes.rb
  Rails.application.routes.draw do
    # ...
    get 'about' => 'static_pages#about', :as => :about
  end
  
  <!-- app/views/static_pages/about.html.erb -->
  <h1>This app has not gotten any more useful...</h1>
  
  <%= link_to 'About', about_path %>
  

Ember

  App.Router.map(function(){
    this.route('about');
  });
  
  <script type="text/x-handlebars" id="about">
    <h2>About</h2>
    <p>PatternLab is a small demo appliction of Ember.</p>
  </script>
  
  <script type="text/x-handlebars">
    <!-- ... -->
    {{#link-to 'about'}}About{{/link-to}}
    <!-- ... -->
  </script>
  
http://emberjs.jsbin.com/falat/3/edit

Aside

Templates

The markup for the stuff thata user actually sees

Handlebars

Handlebars templates are dumb

They can't contain much logic.

Router

Browser URL logic

The 'map' for distinct parts of your app

gives you bookmarkable/shareable URLs within your app

handles reading/writing the URL bar

passes 'path variables' into your Route

http://emberjs.jsbin.com/falat/3#/about

Rendering Partials

Rails

    <!-- in app/views/layouts/application.html.erb -->
    <%= render 'layouts/footer' %>
  
    <!-- app/views/layouts/_footer.html.erb -->
    <hr>&copy; OctoLabs
  

Ember

  <script type="text/x-handlebars">
    <!-- ... -->
    {{partial 'footer'}}
  </script>
  
  <script type="text/x-handlebars" id="_footer">
    <hr/>&copy; OctoLabs
  </script>
  
http://emberjs.jsbin.com/jevexo/1/edit

Displaying Data

Rails

  # app/controllers/static_pages_controller.rb
  class StaticPagesController < ApplicationController
    def about
      @about_data = {
        :version => "0.0.1"
      }
    end
  end
  
  <!-- app/views/static_pages/about.html.erb -->
  <h1>About</h1>
  <p>Version : <%= @about_data[:version] %></p>
  

Route

Controller setup

Model creation/lookup/filtering

Ember

  App.IndexRoute = Ember.Route.extend({
    model : function(){
      return {
        version : "0.0.1"
      };
    }
  });
  
  <script type="text/x-handlebars" id="index">
    <!-- ... -->
    <p>Version : {{version}}</p>
  </script>
  
http://emberjs.jsbin.com/wiyoh/1/edit

Data Binding

  <script type="text/x-handlebars" id="index">
    <!-- ... -->
    <p>Version : {{version}}</p>
    {{input value=version}}
  </script>
  
http://emberjs.jsbin.com/leyog/1/edit

Models

Object wrapper for data

Rails

  # in db/schema.rb
  create_table "patterns" do |t|
    t.integer   "p1"
    t.integer   "p2"
    t.integer   "p3"
    t.datetime "created_at",              :null => false
    t.datetime "updated_at",              :null => false
  end
  
  class Pattern < ActiveRecord::Base
    def name
      "#{p1}-#{p2}-#{p3}"
    end
  end
  

Rails

  resources :patterns
  
  class PatternsController < ApplicationController
    def index
      @patterns = Pattern.all
    end
  end
  
  <h2>Latest Patterns</h2>
  <ul>
    <% @patterns.each do |pattern| %>
      <li><%= pattern.name %></li>
    <% end %>
  </ul>
  

Ember

  App.Pattern = DS.Model.extend({
    p1 : DS.attr('number'),
    p2 : DS.attr('number'),
    p3 : DS.attr('number'),

    name : function(){
      return this.get('p1') + '-' +
             this.get('p2') + '-' +
             this.get('p3');
    }.property('p1','p2','p3')
  });
  
  App.Router.map(function() {
    // ...
    this.resource('patterns');
  });
  

Ember

  App.PatternsRoute = Ember.Route.extend({
    model : function(){
      return [this.store.createRecord('pattern',
        { p1 : 1, p2 : 2, :p3 : 5}
      )];
    }
  });
  
  <script type="text/x-handlebars" id="patterns">
    <h2>Latest Patterns</h2>
    <ul>
      {{#each}}
        <li>{{name}}</li>
      {{/each}}
    </ul>
  </script>
  
http://emberjs.jsbin.com/letok/1/edit

Fixture Adapter

  App.ApplicationAdapter = DS.FixtureAdapter;
  
  App.Pattern.reopenClass({
    FIXTURES: [
      { id: 1, p1 : 1, p2 : 2, p3 : 5},
      { id: 2, p1 : 42, p2 : 142, p3 : 242},
      { id: 3, p1 : 100, p2 : 200, p3 : 300}
    ]
  });
  
  App.PatternsRoute = Ember.Route.extend({
    model : function(){
      return this.store.find('pattern');
    }
  });
  
http://emberjs.jsbin.com/vibeza/2/edit

Dynamic routes & Nested routes and templates

Ember

  App.Router.map(function() {
    this.resource('patterns',function(){
      this.resource('pattern', { path: '/:pattern_id' });
    });
  });
  
  <script type="text/x-handlebars" id="pattern">
    <h3>{{name}}</h3>
  </script>
  
  <script type="text/x-handlebars" id="patterns">
    <h2>Latest Patterns</h2>
    <ul>
      {{#each}}
        <li>
          {{#link-to 'pattern' this}}{{name}}{{/link-to}}
        </li>
      {{/each}}
    </ul>
    {{outlet}}
  </script>
  
http://emberjs.jsbin.com/gekuw/1/edit

Rendering a pattern

Components and/or Views

Which one to use?

Components & Views

Interaction & DOM logic

This is the place for jQuery DOM selectors andintegration with other JS libs.

Probably.

Differences

Component

  • Isolated (No application context)
  • Only knows about whatever you pass in to it

View

  • Not-Isolated (Has appication context)
  • Knows about the controller for the current template

Use Components if possible

Pattern Display Component

  {{pattern-display pattern=model width="256" height="256"}}
  
  <script type="text/x-handlebars" id="components/pattern-display">
    <canvas {{bind-attr id=canvasId
                        width=width
                        height=height
    }}></canvas>
  </script>
  
Chromanin.js
  App.PatternDisplayComponent = Ember.Component.extend({
    drawPattern : function(){
      var ch = new Chromanin(256,this.get('canvasId')),
          p1 = this.get('pattern.p1'),
          p2 = this.get('pattern.p2'),
          p3 = this.get('pattern.p3');
      ch.initlayers(256,256);
      ch.checkerBoardLayer(0,16,16,p1,p2,p3,0,0,128);
      ch.sineDistort(0,0,0.100000001490116,p1,p2,p3);
      ch.addLayers(0,1,4,1,1);
      ch.addLayers(4,2,4,1,1);
      ch.addLayers(4,3,4,1,1);
      ch.writeCanvas();
    }
    // ...
  });
  
  App.PatternDisplayComponent = Ember.Component.extend({
    // ...
    didInsertElement : function(){
      this.drawPattern();
    },

    automaticUpdate : function(){
      Ember.run.next(this,function(){
        this.drawPattern();
      });
    }.observes('pattern','pattern.p1','pattern.p2','pattern.p3'),

    canvasId : function(){
      return "canvas-" + Math.floor((Math.random() * 1000000) + 1);;
    }.property('pattern.id')
  });
  
http://emberjs.jsbin.com/hatev/1/edit

Create a new pattern

  App.Router.map(function() {
    this.resource('patterns',function(){
      this.resource('pattern', { path: '/:pattern_id' });
      this.route('new');
    });
  });
  
  {{#link-to 'patterns.new'}}New Pattern{{/link-to}}
  
  <script type="text/x-handlebars" id="patterns/new">
    <h2>Create a new Pattern</h2>
    {{input value=p1 type='number'}}
    {{input value=p2 type='number'}}
    {{input value=p3 type='number'}}
    <button {{action randomize}}>Randomize</button>
    <button {{action savePattern}}>Save</button>
    <br/><br/>
    {{pattern-display pattern=model width="256" height="256"}}
  </script>
  
  App.PatternsNewRoute = Ember.Route.extend({
    model : function(){
      return this.store.createRecord('pattern');
    }
  });
  
  App.PatternsNewController = Ember.ObjectController.extend({
    actions : {
      savePattern : function(){
        this.transitionToRoute('pattern',this.get('model'));
      },
      randomize : function(){
        this.setProperties({
          p1 : Math.floor((Math.random() * 1000) + 1),
          p2 : Math.floor((Math.random() * 1000) + 1),
          p3 : Math.floor((Math.random() * 1000) + 1)
        });
      }
    }
  });
  
  
http://emberjs.jsbin.com/falat/8/edit

Controllers

Business logic

Manipulaiton of models and triggering of related events.

Also a good place for transaction management.

Controllers come in two flavors

  • ObjectController - represents one single model object.
  • ArrayController - represents a collection of objects.

REST Adapter

  App.ApplicationAdapter = DS.RESTAdapter.extend({
    host : 'http://pattern-lab-api.herokuapp.com'
  });
  
  savePattern : function(){
    var _this = this;
    this.get('model').save().then(function(pattern){
      _this.transitionToRoute('pattern',pattern);
    });
  }
  
http://emberjs.jsbin.com/kimoyo/2/edit

Wrap Up

Wrap Up

Ember is opinionated

Don't try to fight it

Just learn about those opinions

When to use Ember?

When NOT to use Ember?

Thanks For Watching!

@jagthedrummer

jeremy@octolabs.com

http://www.octolabs.com/thatconf2014