0
votes

Coming from the Backbone world the last several years, I wanted to experiment with Ember. I figure I should try creating a mobile-web app, since I've done a lot of work in the mobile web w/ Backbone. I'm trying to build a global nav bar which has a Save button that shows up on certain routes, and the problem I'm stuck on is how to bind/teardown that click event on the Save button in the nav bar to the controller of the current route.

I have a /routines/new route where the app will show the user a form to create a new routine. The navigation bar extends Ember.Evented and propagates the click event via the trigger call. In my App.RoutinesNewRoute, within the didTransition/willTransition events I'm manually doing the binding from App.NavigationController to my App.RoutinesNewController (which contains the logic of how to save the model).

My current approach works, but this means in every route I'll have to manually do the binding/teardown, and it seems like a lot of boilerplate. I'm also envisioning controllers/components that can take advantage of this automatic binding/teardown process that have no direct relationship with the current route (i.e. controllers being inserted via render calls in the templates). I feel like there should be event that I can hook into where the controller "comes into view" so I can write the binding/teardown in one place and not have to think about it, but maybe I'm not understanding the "Ember" way.

So my question: what's the best way to bind/teardown events from a global controller (i.e. nav bar) to any arbitrary controller that's backing the current views in DOM?


Below is the code:

Right now, I have a App.NavigationController and an App.NavigationView that gets rendered into the application layout:

Navigation Controller

App.NavigationController = Ember.ObjectController.extend Ember.Evented,
  isShowing: false
  showBack: false
  title: ""
  rightButtonTitle: ""

  navigationStyle: (->
    if @get('isShowing')
      ""
    else
      "display: none;"
  ).property('isShowing')

  backStyle: (->
    if @get('showBack')
      ""
    else
      "display: none;"
  ).property('showBack')

  rightButtonStyle: (->
    if @get('rightButtonTitle')
      ""
    else
      "display:none"
  ).property('rightButtonTitle')

  actions:
    rightButtonClick: ->
      @trigger 'right-button'
    backButtonClick: ->
      @trigger 'back-button'
    reset: ->
      @setProperties
        isShowing: false
        title: ""
        rightButtonTitle: ""

App.NavigationView:

App.NavigationView = Ember.View.extend
  templateName: 'navigation'
  actions:
    rightButtonClick: ->
      @get('controller').send('rightButtonClick')
    backButtonClick: ->
      @get('controller').send('backButtonClick')

application.hbs

{{render 'navigation'}}
{{outlet}}

App.RoutinesNewRoute

App.RoutinesNewRoute = Ember.Route.extend
  model: ->
    if routine = @controllerFor('application').get('routine')
      routine
    else
      @store.find('routine').then (routines) =>
        nextDay = routines.content.length + 1

        promise = @store.find('exercise').then (exercises) =>
          exercises = exercises.map (e) ->
            id: e.get('id')
            name: e.get('name')
            weight: e.get('weight')

          routine = @store.createRecord 'routine',
            exercises: exercises
            day: nextDay

          @controllerFor('application').set('routine', routine)
          routine


  actions:
    didTransition: ->
      @_super()
      # This is where I do all the manual event binding
      @controllerFor('navigation').on 'right-button', @, =>
        @controller.save().then =>
          @controllerFor('application').set('routine', null)
        @transitionTo('routines')

      @controllerFor('navigation').on 'back-button', @, =>
        @transitionTo('routines')

      @controllerFor('navigation').setProperties
        isShowing: true
        showBack: true
        title: "New Routine"
        rightButtonTitle: "Save"


    willTransition: ->
      # This is where I do all the manual event teardown
      @_super()
      @controllerFor('navigation').off 'right-button', @
      @controllerFor('navigation').off 'back-button', @
      @controllerFor('navigation').send('reset')

App.RoutinesNewController

App.RoutinesNewController = Ember.ObjectController.extend
  save: -> @get('model').save()

  actions:
    save: -> @save()
1

1 Answers

1
votes

What you can do is extract the "save through navigation" behavior in a mixin. I'm not very familiar with CoffeeScript so the following is a mix of Javascript and CoffeeScript.

App.SaveableThroughNavigationMixin = Ember.Mixin.create({
  doEventBindings: function() {
    @_super()
    # This is where I do all the manual event binding
    @controllerFor('navigation').on 'right-button', @, =>
      @controller.save().then =>
        @controllerFor('application').set('routine', null)
      @transitionTo('routines')

    @controllerFor('navigation').on 'back-button', @, =>
      @transitionTo('routines')

    @controllerFor('navigation').setProperties
      isShowing: true
      showBack: true
      title: "New Routine"
      rightButtonTitle: "Save"
  }.on('didTransition'),
  doEventTeardowns: function() {
    # This is where I do all the manual event teardown
    @_super()
    @controllerFor('navigation').off 'right-button', @
    @controllerFor('navigation').off 'back-button', @
    @controllerFor('navigation').send('reset')
  }.on('willTransition')
});

Then for every Route that you want this behavior, you include the mixin.

App.RoutinesNewRoute = Ember.Route.extend(App.SaveableThroughNavigationMixin, {
  model: function() {
    ...
  }
});

I hope this helps.