Double Click to Edit View in Ember

When writing my current Ember app I wanted a view where you can double click to edit an element and it will replace the current item with a textfield. Instead of writing multiple views to handle this double click to edit feature, I wanted to write a reusable view that I can bind a value to and forget about.

The code before making a reusable view out of it was a bit messy. First we had to have a triggerView which would trigger the editingName property in the controller which would then show our text field. This text field would respond to the focusOut event and call the save method in the controller and change editingName in our controller back to false. There were too many working parts in different places to make the code feel clean.

Here's the layout for our new reusable view.


{{#if view.isEditing}}
  {{view view.textField valueBinding=view.value action=save}}
{{else}}
 {{yield}}
{{/if}}

All we're really doing is checking if view.isEditing is true and show our custom text field if true. The custom text field is nested inside our view and has an action of save which means when the textfield has the enter key pressed it will call our save method. If the view property isEditing isn't true, then we just show our passed in block.

Actually using the custom view looks like this.


{{#view App.InlineTextField valueBinding=name}}
  <h1 id="name">{{name}}</h1>
{{/view}}

If you don't know or haven't put it together, <h1 id="name"></h1> is our passed in block.

The front end is simple enough but the back end is just a tiny bit tricker.

App.InlineTextField = Ember.View.extend({
  layoutName: 'inline/text_field',

  doubleClick: function() {
    if (!this.get('isEditing'))  {
      this.set('isEditing', true);
      Ember.run.scheduleOnce('afterRender', this, this.focusTextField);
    }
  },

  focusTextField: function() {
    var val = this.$('input').val();
    this.$('input').focus();

    this.$('input').val('');
    this.$('input').val(val);
  },

  textField: Ember.TextField.extend({
    focusOut: function() {
      this.save();
    },

    save: function() {
      var parentView = this.get('parentView');
      var controller = parentView.get('controller');

      if (controller.save) {
        controller.save();
      }
      parentView.set('isEditing', false);
    },
  }),
});

There's quite a bit to get into here, so let's go line by line. First we set our layout path (the first snippet I showed off) to inline/text_field which corresponds to the file inline/text_field.handlebars.

Next we handle our doubleClick event. When you double click our view while isEditing is false we set it to be true and tell Ember to call our this.focusTextField function after the next render. If we didn't call Ember.scheduleOnce we would still have the passed in block rendered instead of the text field.

The focusTextField method is for setting our cursor to the end of the line. We have to make the text field empty and then put in the original value to make the cursor start at the end of the line rather that the beginning.

Finally we have our custom text field, textField. All we do is inherit from Ember.TextField and add a focusOut event to call our save method. Our save method calls the save method on its controller if it exists and always sets our views isEditing property to false.

There are a lot of things that could be done differently such as setting value inside of our view rather than passing in a block, or even modifying the code to use a text area instead of a text field. Overall this should be a good starting point to do similar things with views and learn a few things about Ember and view rendering.