Slide 1

Slide 1 text

Intro to the Ember Router @james_croft

Slide 2

Slide 2 text

Boilerplate for an app with a router

Slide 3

Slide 3 text

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();

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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({})

Slide 7

Slide 7 text

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();

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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({ //... })

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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.

Slide 16

Slide 16 text

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(); <h1>Contacts</h1>

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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', ... })

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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...

Slide 25

Slide 25 text

gives you

Slide 26

Slide 26 text

Adding the first state

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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.

Slide 33

Slide 33 text

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...

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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.

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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...

Slide 38

Slide 38 text

gives you

Slide 39

Slide 39 text

gives you Index page has been connected to the outlet

Slide 40

Slide 40 text

Adding a second state

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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') } }) }) })

Slide 43

Slide 43 text

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') } }) }) })

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

Switching between states

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

Adding data

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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') } }) }) })

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

Refresh the page

Slide 63

Slide 63 text

The name is here Other info is missing

Slide 64

Slide 64 text

• 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.

Slide 65

Slide 65 text

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); } }) }) })

Slide 66

Slide 66 text

Refresh the page

Slide 67

Slide 67 text

WIN!

Slide 68

Slide 68 text

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();

Slide 69

Slide 69 text

What is an outlet?

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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.

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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