0
votes

I'm trying to write an Ember view that has three states. Specifically, a submit button that transitions from "Submit" to "Saving..." to "Finished!" There are many ways to accomplish this goal, but I was wondering what the "best practice" would be from an Ember standpoint to accomplish this without writing crappy code.

Currently I have the following code:

UiControls.SubmitButton = Ember.View.extend({

  template: function() {  
    var template = '{{#if view.isNotStarted}}Submit{{/if}}';
    template += '{{#if view.isStarted}} <i class="icon-spinner icon-spin"></i>Saving...{{/if}}';
    template += '{{#if view.isFinished}} <i class="icon-check-sign"></i>Finished!{{/if}}'
    return Ember.Handlebars.compile(template);
  }.property(),

  isNotStarted: true,
  isStarted: null,
  isFinished: null,

  classNames: ['btn', 'btn-green'],

  isDisabled: false,

  click: function(){
    if (!this.get('disabled')){
      this.set('isNotStarted', false);
      this.set('isStarted', true);
      this.set('isFinished', false);
      this.timer();
    }
  },

  /* Simulates a server call */
  timer: function(){
    (function(self){
      setTimeout(function(){
        self.set('isStarted', false);
        self.set('isFinished', true);
      }, 500);
    })(this);
  }
});

To me this is really ugly -- we're setting individual boolean values based off of events in order to work with handlebars' purposefully restricted conditional syntax.

What I want is a handlebars construct that accepts something like an Ember StateManager property (not possible with Handlebars syntax). Or, at the very least, I want to alter my template based off of a computed property from a StateManager (again, not possible). So my question is, is there any better way to write the above code to prevent code duplication handling state transitions manually through lots of little boolean flag manipulations?

1

1 Answers

2
votes

To me this is really ugly -- we're setting individual boolean values based off of events in order to work with handlebars' purposefully restricted conditional syntax.

Totally agreed, this is a sign that some refactoring is needed.

What I want is a handlebars construct that accepts something like an Ember StateManager property (not possible with Handlebars syntax).

It's possible if you write a custom handlebars helper, but honestly I would not recommend that approach.

Or, at the very least, I want to alter my template based off of a computed property from a StateManager (again, not possible)

Why not? Guessing you mean even if you had that property it's not possible to alter the template without all the booleans.

So my question is, is there any better way to write the above code to prevent code duplication handling state transitions manually through lots of little boolean flag manipulations?

Yes. The reason handlebars has this restriction is to prevent complexity and logic from being part of your templates. For example any time you need to show like 1-of-3 versions based on some value. That kind of logic belongs in the view or controller layer.

So looking at your example, there are two aspects of the template that need to change

  • text: Should be either "Submit", "Saving..." or "Finished!"
  • iconClassNames: Either empty, "icon-spinner icon-spin" or "icon-check-sign"

With this in mind we can simplify the template to be:

<i {{bindAttr class="view.iconClassNames"></i>{{view.text}}

And add the properties to the view

UiControls.SubmitButton = Ember.View.extend({
  template: Ember.Handlebars.compile('<i {{bindAttr class="view.iconClassNames"></i>{{view.text}}'),
  classNames: ['btn', 'btn-green'],
  isDisabled: false,
  text: "Submitted",
  iconClassNames: "",
  click: function(){
    if (!this.get('disabled')){
      this.set('text', 'Saving...');
      this.set('iconClassNames', 'icon-spinner icon-spin');
      this.timer();
    }
  },

  /* Simulates a server call */
  timer: function(){
    (function(self){
      setTimeout(function(){
      this.set('text', 'Finished!');
      this.set('iconClassNames', 'icon-check-sign');
      }, 500);
    })(this);
  }
});

This works for the simulation but is not ideal. Really you want text and iconClassNames to be bound to the stateManager. That means changing the text and iconClassNames to be computed properties. Ideally they would be computed based on the underlying state of the model object, and click() would be defined on controller, but for simulation it would be something like this:

UiControls.SubmitButton = Ember.View.extend({
  template: Ember.Handlebars.compile('<i {{bindAttr class="view.iconClassNames"></i>{{view.text}}'),
  classNames: ['btn', 'btn-green'],
  isDisabled: false,
  state: 'new',
  text: function() {
    //return appropriate button text based on state
  }.property('state'),
  iconClassNames: function() {
    //calculate text based on state
  }.property('state'),

  /* Simulates a server call */
  click: function(){
    if (!this.get('disabled')){
      this.set('state', 'saving');
      this.timer();
    }
  },

  /* Simulates a server call */
  timer: function(){
    (function(self){
      setTimeout(function(){
        self.set('state', 'finished');
      }, 500);
    })(this);
  }
});