Zack Hubert

Ember.js and Rails, Part 3: Client Routing and Layout

In the first part of this series, I built a working Rails server vending a super simple API. In the second post, I made the simple connection to model level integration in the client. Now, let’s see if we can get a single resource working in the client, just like a scaffolded server side implementation. It’s not going to be super useful, but it should be a building block for more useful things.

So, we’ll need a working router, controller, views, and templates. Sounds like a few posts…ah well.

Starting with the router, the first thing I noticed is that there isn’t really one baked into Ember.js yet. There’s some possibilities from the Sproutcore days, but I couldn’t tell if they were canonical or not. However, there is a cool library by Gordon Hempton appropriately named ember-routemanager which he covers on his blog. With an emerging framework like Ember.js, the patterns for best practices are likely to change, so perhaps this will be merged into the framework at some point.

Anticipating a Rails-like client side app, I’d also like a solution to managing my views and composition of subviews (to be a good memory footprint citizen in addition to partials-like functionality), again Gordon has a nice approach for that ember-layout. Thanks Gordon!

Rather than gemify these into the asset pipeline, let’s just copy them into app/assets/javascripts/ember/lib and add them to application.js like so:

1
2
//= require ember/lib/ember-routemanager
//= require ember/lib/ember-layout

Now we need to establish some routes and layouts, trying to minimize impedance from the Rails way, let’s put this into it’s own file:

app/assets/javascripts/ember/router/router.js.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# changes the active state for nav
App.NavState = Ember.LayoutState.extend(
  navSelector: ".navbar .nav"
  enter: (stateManager, transition) ->
    @_super stateManager, transition
    $nav = $(@get("navSelector"))
    $nav.children().removeClass "active"
    selector = @get("selector") or ("." + @get("path"))
    $nav.find(selector).addClass "active"
)
# TODO - page doesn't exist for initial state change
# so initial active class isn't appended

# have to place this into the DOM ourselves
App.mainView = App.MainLayoutView.create()

App.router = Ember.RouteManager.create(
  enableLogging: true

  # every view hangs off this one
  rootView: App.mainView

  # Home
  home: App.NavState.create(
    selector: '.home'
    viewClass: App.MainHomeView
  )

  # posts stack
  posts: App.NavState.create(
    selector: ".posts"
    route: "posts"
    viewClass: App.PostsLayoutView
    # posts#index
    index: Ember.LayoutState.create(
      viewClass: App.PostsIndexView
      enter: (stateManager,transition) ->
        @_super stateManager, transition
        App.postsController.findAll()
    )
    # posts#new
    newPost: Ember.LayoutState.create(
      route: "new"
      viewClass: App.PostsNewView
    )
    # posts#show
    show: Ember.LayoutState.create(
      route: ":postId"
      viewClass: App.PostsShowView
      enter: (stateManager, transition) ->
        @_super stateManager, transition
        postId = stateManager.getPath("params.postId")
        App.postsController.selectPost(postId)
        #@get("view").set "post", post
    )
  )
)

And require it in app.js.coffee under templates:

1
#= require_tree ./router

As you can see, I’ve enabled logging so we can see if the router is doing its thing. I’ve also composed my routes differently than single page example apps. I have added a show view so I can have a detail page for this resource, and later you’ll see that I have put edit and delete on show. I have also made new it’s own page separate from index. My goal is Rails idiom matching, not SPA, so it makes more code than necessary for this trivial case. I’m trying to make a good skeleton out of the gate that will grow with the app complexity. Seeing another SPA won’t help me experience Ember for a complex Rails-like app.

None of these routes will work without the views and templates being defined, so we better move on to part 4!

Credits and Helpful Links:

Comments