Skip to content

Instantly share code, notes, and snippets.

@jamesarosen
Created January 11, 2012 00:21
Show Gist options
  • Save jamesarosen/1592144 to your computer and use it in GitHub Desktop.
Save jamesarosen/1592144 to your computer and use it in GitHub Desktop.
On Ember Views & States

I have a state machine for the major sections of the page:

App.States = Ember.StateManger.create({
  foo: Ember.State.create({})
});

I have a view that needs to reset itself whenever the user enters the foo state:

App.FooDisclosureView = Ember.View.extend({
  didInsertElement: function() {
    // whenever the button is pressed, toggle the disclosure:
    var self = this;
    this.$('button').click(function() {
      self.get('disclosedContent').toggleProperty('isVisible');
    });
  },

  reset: function() {
    // close the disclosure if it's open
    this.get('disclosedContent').set('isVisible', false);
  }
});

The question is how to call FooDisclosureView#reset when that state is entered.

Option 1: currentState binding

App.FooDisclosureView = Ember.View.extend({
  // as above...

  resetOnEnterStateFoo: function() {
    // if foo has child states, you can use a more intelligent matcher
    if (App.getPath('States.currentState.name') === 'foo') {
      this.reset();
    }
  }.observes('App.States.currentState.name')
});

Option 2: Controller Mediation

Essentially the same as 1, but with a Controller.

App.fooController = Ember.Object.create({
  active: false
});

App.States = Ember.StateManger.create({
  foo: Ember.State.create({
    enter: function() { App.setPath('fooController.active', true); },
    exit:  function() { App.setPath('fooController.active', false); }
  })
});

App.FooDisclosureView = Ember.View.extend({
  // as above...

  resetOnEnterStateFoo: function() {
    if (App.getPath('fooController.active')) {
      this.reset();
    }
  }.observes('App.fooController.active')
});

Option 3: State Change Events

This option requires changing Ember.StateManager to emit events when states change. This could be a monkey-patch or, if generally useful, incorporated into the library.

App.FooDisclosureView = Ember.View.extend({
  init: function() {
    Ember.addListener(
      App.States,
      'stateChange',
      this,
      this.resetOnEnterStateFoo
    );
  },

  resetOnEnterStateFoo: function(newState, oldState) {
    if (newState.get('name') === 'foo') { this.reset(); }
  },

  // as above...
});
@jamesarosen
Copy link
Author

There's also the option of having each instance of FooDisclosureView register itself globally (e.g. App.FooDisclosureView.instances) and having the state run through them and reset them all. We'll just say that's not very Ember.

@rapheld
Copy link

rapheld commented Jan 11, 2012

I generally don't care for Option 1. It feels clunky given how clean states are. Option 2 feels most the like Ember, so would say it is a fine option. Options 3 feels more like pre-ember JS. I am a fan of custom events as they're great at decoupling. I like how this feels and wouldn't oppose it, but I do feel like this is basically the same functionality Ember offers us.

@jish
Copy link

jish commented Jan 11, 2012

I would throw the entire reset idea out the window and just use viewStates. Each time you enter a viewState it "resets" itself: http://jsfiddle.net/x5AM5/2/.

If you are dependent on a property, I would use the enter: method and a property on a controller:

enter: function() {
  controller.set('disclosureVisible', false);
}

@jamesarosen
Copy link
Author

There are a few things I don't like about ViewStates:

  1. you have to declare the view property when you create the state. Since states can't be added to a StateManager after it's built, that means we need a whole bunch of view code in our states code.
  2. we might need to reset several different views when we enter a state. I suppose we could use one outer view and have it control its children.
  3. they are incompatible with body-templates (unless we used named singleton views, which I think is an antipattern) since the state needs to point to a particular view rather than all instances of a view class.

@jish
Copy link

jish commented Jan 12, 2012

If viewStates won't work, then I would suggest the enter: method I proposed above, or the pattern you suggested earlier:

foo: Ember.State.create({
  disclosureVisible: true
})

then:

someBinding: 'currentState.disclosureVisible

edit: Nm, that pattern doesn't take into account the toggling nature you suggest :x

@jamesarosen
Copy link
Author

No, though it does suggest a slightly different approach:

foo: Ember.State.create({
  fooDisclosureAvailable: true
})
...
App.FooDisclosureView = Ember.View.extend({
  resetObserver: function() {
    if (this.getPath("App.States.currentState.fooDisclosureAvailable")) {
      // The observer only fires when this has just been changed and it's
      // now true, so it must have changed from false:
      this.reset();
    }
  }.observes("App.States.currentState.fooDisclosureAvailable")
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment