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