Upgrade to Pro — share decks privately, control downloads, hide ads and more …

An introduction to the Ember router

crofty
September 05, 2012
2.3k

An introduction to the Ember router

A talk from the London Ember.js User Group.

crofty

September 05, 2012
Tweet

Transcript

  1. Intro to the Ember
    Router
    @james_croft

    View Slide

  2. Boilerplate for an app with a router

    View Slide

  3. Contacts = Ember.Application.create();
    Contacts.ApplicationController = Ember.Controller.extend({});
    Contacts.ApplicationView = Ember.View.extend({
    templateName: 'application'
    })
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({})
    })
    Contacts.initialize();

    View Slide

  4. Contacts = Ember.Application.create();
    Contacts.ApplicationController = Ember.Controller.extend({});
    Contacts.ApplicationView = Ember.View.extend({
    templateName: 'application'
    })
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({})
    })
    Contacts.initialize();
    Create the application

    View Slide

  5. Contacts = Ember.Application.create();
    Contacts.ApplicationController = Ember.Controller.extend({});
    Contacts.ApplicationView = Ember.View.extend({
    templateName: 'application'
    })
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({})
    })
    Contacts.initialize();
    Define the application controller
    and view

    View Slide

  6. Controllers & Views
    • Each view is backed by a controller.
    • Controllers proxy the models and add client-
    side application state.
    • They need to be named xView and xController
    App.ApplicationView = Ember.View.extend({})
    App.ApplicationController = Ember.Controller.extend({})
    App.IndexView = Ember.View.extend({})
    App.IndexController = Ember.Controller.extend({})

    View Slide

  7. Contacts = Ember.Application.create();
    Contacts.ApplicationController = Ember.Controller.extend({});
    Contacts.ApplicationView = Ember.View.extend({
    templateName: 'application'
    })
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({})
    })
    Contacts.initialize();

    View Slide

  8. Naming conventions
    Assign classes to uppercase names
    App.Person = Ember.Object.extend({})
    App.PersonView = Ember.View.extend({})
    App.PersonController = Ember.Controller.extend({})

    View Slide

  9. Naming conventions
    Assign instances to lowercase names
    App.john = App.Person.create()
    App.personController = Ember.Controller.create()

    View Slide

  10. Naming conventions
    • Uppercase when using extend
    • Lowercase when using create

    View Slide

  11. Naming conventions
    There’s an exception.
    Namespaces are always uppercase
    NamespaceOne = Ember.Namespace.extend({})
    NamespaceTwo = Ember.Namespace.create()

    View Slide

  12. Contacts = Ember.Application.create();
    Contacts.ApplicationController = Ember.Controller.extend({});
    Contacts.ApplicationView = Ember.View.extend({
    templateName: 'application'
    })
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({})
    })
    Contacts.initialize();
    The application is a namespace

    View Slide

  13. Contacts = Ember.Application.create();
    Contacts.ApplicationController = Ember.Controller.extend({});
    Contacts.ApplicationView = Ember.View.extend({
    templateName: 'application'
    })
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({})
    })
    Contacts.initialize();
    Ember.Application = Ember.Namespace.extend({
    //...
    })

    View Slide

  14. Contacts = Ember.Application.create();
    Contacts.ApplicationController = Ember.Controller.extend({});
    Contacts.ApplicationView = Ember.View.extend({
    templateName: 'application'
    })
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({})
    })
    Contacts.initialize();
    so even though it is an instance, it is uppercased

    View Slide

  15. Contacts = Ember.Application.create();
    Contacts.ApplicationController = Ember.Controller.extend({});
    Contacts.ApplicationView = Ember.View.extend({
    templateName: 'application'
    })
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({})
    })
    Contacts.initialize();
    Everything else uses extend.

    View Slide

  16. Contacts = Ember.Application.create();
    Contacts.ApplicationController = Ember.Controller.extend({});
    Contacts.ApplicationView = Ember.View.extend({
    templateName: 'application'
    })
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({})
    })
    Contacts.initialize();
    <br/><h1>Contacts</h1><br/>

    View Slide

  17. Contacts = Ember.Application.create();
    Contacts.ApplicationController = Ember.Controller.extend({});
    Contacts.ApplicationView = Ember.View.extend({
    templateName: 'application'
    })
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({})
    })
    Contacts.initialize();
    Subclass Ember.Router.
    This must be assigned to a property
    named Router

    View Slide

  18. Contacts = Ember.Application.create();
    Contacts.ApplicationController = Ember.Controller.extend({});
    Contacts.ApplicationView = Ember.View.extend({
    templateName: 'application'
    })
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({})
    })
    Contacts.initialize();
    Options are:
    hash, history (pushState), none

    View Slide

  19. Contacts = Ember.Application.create();
    Contacts.ApplicationController = Ember.Controller.extend({});
    Contacts.ApplicationView = Ember.View.extend({
    templateName: 'application'
    })
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({})
    })
    Contacts.initialize();
    Define a route named ‘root’. This
    will act as a container for all other
    states.
    The router will transition to this
    state upon initialisation

    View Slide

  20. Contacts = Ember.Application.create();
    Contacts.ApplicationController = Ember.Controller.extend({});
    Contacts.ApplicationView = Ember.View.extend({
    templateName: 'application'
    })
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({})
    })
    Contacts.initialize();
    Ember.Router = Ember.StateManager.extend({
    initialState: 'root',
    ...
    })

    View Slide

  21. Contacts = Ember.Application.create();
    Contacts.ApplicationController = Ember.Controller.extend({});
    Contacts.ApplicationView = Ember.View.extend({
    templateName: 'application'
    })
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({})
    })
    Contacts.initialize();
    Initialise the application

    View Slide

  22. Contacts = Ember.Application.create();
    Contacts.ApplicationController = Ember.Controller.extend({});
    Contacts.ApplicationView = Ember.View.extend({
    templateName: 'application'
    })
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({})
    })
    Contacts.initialize();
    • Initialise an instance of the
    router
    • Look through the namespace
    for anything named xController
    and initialise it as a property on
    the router

    View Slide

  23. Contacts.get('router').toString()
    //
    Contacts.get('router.applicationController').toString()
    //
    Post initialisation
    An instance of the router has been
    created and is available on the
    application namespace
    An instance of the
    ApplicationController has been
    created and is available on the
    router instance

    View Slide

  24. Contacts = Ember.Application.create();
    Contacts.ApplicationController = Ember.Controller.extend({});
    Contacts.ApplicationView = Ember.View.extend({
    templateName: 'application'
    })
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({})
    })
    Contacts.initialize();
    Putting it all together...

    View Slide

  25. gives you

    View Slide

  26. Adding the first state

    View Slide

  27. Contacts.IndexController = Ember.ArrayController.extend({});
    Contacts.IndexView = Ember.View.extend({
    templateName: 'index'
    })
    <br/><h2>Index</h2><br/>
    Add the controller, view and template

    View Slide

  28. Add an outlet to the application template
    <br/><h1>Contacts</h1><br/>{{outlet}}<br/>
    Outlets are things that we ‘plug’ views into.

    View Slide

  29. Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({
    index: Ember.Route.extend({
    route: '/',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet('index')
    }
    })
    })
    })
    Add the new state

    View Slide

  30. Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({
    index: Ember.Route.extend({
    route: '/',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet('index')
    }
    })
    })
    })
    Add the new state
    Leaf states have a route property which
    is set to a url.
    When the browser url matches this url,
    the application will transition into this
    state

    View Slide

  31. Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({
    index: Ember.Route.extend({
    route: '/',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet('index')
    }
    })
    })
    })
    Add the new state
    When the application first loads, the
    browser will have a url of ‘/‘ and the
    router will transition into this state

    View Slide

  32. Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({
    index: Ember.Route.extend({
    route: '/',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet('index')
    }
    })
    })
    })
    Add the new state
    connectOutlets is a callback that gets
    triggered when the application
    transition into the state.
    We use the callback to plug the views
    into any outlets that we’ve defined in
    the templates.

    View Slide

  33. Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({
    index: Ember.Route.extend({
    route: '/',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet('index')
    }
    })
    })
    })
    Add the new state
    We grab our application controller
    instance from the router...

    View Slide

  34. Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({
    index: Ember.Route.extend({
    route: '/',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet('index')
    }
    })
    })
    })
    Add the new state
    and call connectOutlet on it, passing
    ‘index’ as an argument

    View Slide

  35. Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({
    index: Ember.Route.extend({
    route: '/',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet('index')
    }
    })
    })
    })
    Add the new state
    ‘index’ means that an instance of IndexView will be
    created and plugged into the outlet.
    The view will be backed by the instance of
    IndexController that was initialised for us.

    View Slide

  36. Magic
    .connectOutlet('index')
    Less Magic
    .connectOutlet({
    view: Contacts.IndexView
    controller: router.get('indexController')
    })

    View Slide

  37. Contacts = Ember.Application.create();
    Contacts.ApplicationController = Ember.Controller.extend({});
    Contacts.ApplicationView = Ember.View.extend({
    templateName: 'application'
    })
    Contacts.IndexController = Ember.ArrayController.extend({});
    Contacts.IndexView = Ember.View.extend({
    templateName: 'index'
    })
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({
    index: Ember.Route.extend({
    route: '/',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet('index')
    }
    })
    })
    })
    Contacts.initialize();
    Putting it all together...

    View Slide

  38. gives you

    View Slide

  39. gives you
    Index page has been
    connected to the outlet

    View Slide

  40. Adding a second state

    View Slide

  41. Controller, View & template
    Contacts.ShowController = Ember.ObjectController.extend({});
    Contacts.ShowView = Ember.View.extend({
    templateName: 'show'
    })
    <br/><h2>Show</h2><br/>

    View Slide

  42. Route
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({
    index: Ember.Route.extend({
    route: '/',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet('index')
    }
    }),
    show: Ember.Route.extend({
    route: '/show',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet('show')
    }
    })
    })
    })

    View Slide

  43. Route
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({
    index: Ember.Route.extend({
    route: '/',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet('index')
    }
    }),
    show: Ember.Route.extend({
    route: '/show',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet('show')
    }
    })
    })
    })

    View Slide

  44. View Slide

  45. Switching between states

    View Slide

  46. Put some links in the templates
    <br/><h2>Index page</h2><br/><a {{action goToShow href=true}}>Go to show</a><br/>
    <br/><h2>Show page</h2><br/><a {{action goToIndex href=true}}>Go to index</a><br/>

    View Slide

  47. Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({
    goToIndex: Ember.Route.transitionTo('index'),
    goToShow: Ember.Route.transitionTo('show'),
    index: Ember.Route.extend({
    route: '/',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet('index')
    }
    }),
    show: Ember.Route.extend({
    route: '/show',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet('show')
    }
    })
    })
    })
    Add the transition methods

    View Slide

  48. • Click link
    • goToShow action is called on the router
    • Application transitions into show state

    View Slide

  49. Adding data

    View Slide

  50. Data
    Ember.Object.create({
    name: 'James Croft',
    city: 'London',
    //...
    })
    Contacts are simple Ember Objects created with
    data pulled from the meetup.com API

    View Slide

  51. Data API
    Contacts.Data.all()
    // returns an array of all the contacts
    Contacts.Data.findByName('james')
    // returns the contact with name james

    View Slide

  52. Change our index template to list all the
    contacts
    <br/><ul><br/>{{#each contact in controller}}<br/><li><a {{action showContact contact href=true}}>{{contact.name}}</a></li><br/>{{/each}}<br/></ul><br/>

    View Slide

  53. Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({
    goToIndex: Ember.Route.transitionTo('index'),
    goToShow: Ember.Route.transitionTo('show'),
    index: Ember.Route.extend({
    route: '/',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet({
    name: 'index',
    context: Contacts.Data.all()
    });
    }
    }),
    show: Ember.Route.extend({
    route: '/show',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet('show');
    }
    })
    })
    })
    Pass the data to the controller

    View Slide

  54. View Slide

  55. <br/><ul><br/>{{#each contact in controller}}<br/><li><a {{action showContact contact href=true}}>{{contact.name}}</a></li><br/>{{/each}}<br/></ul><br/>

    View Slide

  56. Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({
    goToIndex: Ember.Route.transitionTo('index'),
    showContact: Ember.Route.transitionTo('show'),
    index: Ember.Route.extend({
    route: '/',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet({
    name: 'index',
    context: Content.Data.all()
    })
    }
    }),
    show: Ember.Route.extend({
    route: '/show/:name',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet('show')
    }
    })
    })
    })

    View Slide

  57. View Slide

  58. Contacts.router.urlForEvent('showContact', {name: 'james'})
    // #/show/james
    Here’s how:

    View Slide

  59. Change our show template to list the
    contact info
    <br/><h2>{{name}}</h2><br/><img {{bindAttr src="photo"}}/><br/><dl><br/><dt>Bio</dt><br/><dd>{{bio}}</dd><br/><dt>City</dt><br/><dd>{{city}}</dd><br/><dt>Topics</dt><br/><dd><br/><ul><br/>{{#each topic in topics}}<br/><li>{{topic}}</li><br/>{{/each}}<br/></ul><br/></dd><br/></dl><br/>

    View Slide

  60. Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({
    goToIndex: Ember.Route.transitionTo('index'),
    showContact: Ember.Route.transitionTo('show'),
    index: Ember.Route.extend({
    route: '/',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet({
    name: 'index',
    context: Content.Data.all()
    })
    }
    }),
    show: Ember.Route.extend({
    route: '/show/:name',
    connectOutlets: function(router,context){
    router.get('applicationController').connectOutlet({
    name: 'show',
    context: context
    })
    }
    })
    })
    })
    Pass the data to the controller

    View Slide

  61. View Slide

  62. Refresh the page

    View Slide

  63. The name is here
    Other info is missing

    View Slide

  64. • Entering the application at a particular URL
    doesn’t give us access to the previously
    loaded data.
    • We need something to convert the params
    hash from the matched url into an object
    url http://localhost:3000/#/show/James Croft
    matches route /show/:name
    with params {name: 'James Croft'}
    We need to use our params to find our contact.

    View Slide

  65. Contacts.Router = Ember.Router.extend({
    //...
    show: Ember.Route.extend({
    route: '/show/:name',
    connectOutlets: function(router,context){
    router.get('applicationController').connectOutlet({
    name: 'show',
    context: context
    })
    },
    deserialize: function(router, urlParams){
    return Contacts.Data.findByName(urlParams.name);
    }
    })
    })
    })

    View Slide

  66. Refresh the page

    View Slide

  67. WIN!

    View Slide

  68. Contacts.ApplicationController = Ember.Controller.extend({});
    Contacts.ApplicationView = Ember.View.extend({
    templateName: 'application'
    })
    Contacts.IndexController = Ember.ArrayController.extend({});
    Contacts.IndexView = Ember.View.extend({
    templateName: 'index'
    })
    Contacts.ShowController = Ember.ObjectController.extend({});
    Contacts.ShowView = Ember.View.extend({
    templateName: 'show'
    })
    Contacts.Router = Ember.Router.extend({
    location: 'hash',
    root: Ember.Route.extend({
    goToIndex: Ember.Route.transitionTo('index'),
    showContact: Ember.Route.transitionTo('show'),
    index: Ember.Route.extend({
    route: '/',
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet({
    name: 'index',
    context: Contacts.Data.all()
    });
    }
    }),
    show: Ember.Route.extend({
    route: '/show/:name',
    connectOutlets: function(router, context){
    router.get('applicationController').connectOutlet({
    name: 'show',
    context: context
    })
    },
    deserialize: function(router, urlParams){
    return Contacts.Data.findByName(urlParams.name);
    }
    })
    })
    })
    Contacts.initialize();

    View Slide

  69. What is an outlet?

    View Slide

  70. In our view we have:
    {{ outlet }}
    This is the same as:
    {{view Ember.ContainerView currentViewBinding=”controller.view”}}

    View Slide

  71. So we can replace:
    connectOutlets: function(router){
    router.get('applicationController').connectOutlet({
    name: 'index',
    context: Contacts.Data.all()
    });
    }
    With:
    connectOutlets: function(router){
    controller = router.get('indexController')
    controller.set('content', Contacts.Data.all())
    view = Contacts.IndexView.create({controller: controller})
    router.set('applicationController.view', view)
    }
    And it all still works.

    View Slide

  72. An outlet just refers to an instance of a view on the controller.
    You can set these up manually if you need it.

    View Slide

  73. Resources
    • http://trek.github.com/
    • http://www.thesoftwaresimpleton.com/blog/
    2012/08/20/routing_update/
    • https://speakerdeck.com/u/tomdale/p/emberjs-
    more-than-meets-the-eye

    View Slide