Opinionated Programmer - Jo Liss's musings on enlightened software development.

Ember.js: Implementing text fields with save buttons

[Note: This post uses Ember master syntax. On 0.9.8.1, you may have to remove view. in the *Binding attributes.]

Live Updating

Ember.TextField and friends live-update their value property as the user enters text. You can use valueBinding to bind to some model property, like so:

1
{{view Ember.TextField valueBinding="firstName"}}

Now firstName will be updated immediately as the user enters text.

With Save button

Oftentimes, live updating is not what you want. Instead, you want to update the model data only when the user clicks a Save button.

Plain input field

1
2
3
4
5
6
7
8
App.LazyTextField = Ember.View.extend({
  attributeBindings: ['value', 'type', 'size', 'name', 'placeholder', 'disabled', 'maxlength'],
  tagName: 'input',
  type: 'text',
  getCurrentValue: function() {
    return this.$().val();
  }
});

This is a lazy-updating text field. Whenever you set or update its value property, the contents get updated, but when the user changes the contents, they are not written back into value.

Now we can implement a save button like so:

1
2
3
4
5
6
7
8
9
10
11
12
App.PersonView = Ember.View.extend({
  firstName: 'Jane',
  template: Ember.Handlebars.compile(
    '<form>' +
    '{{view App.LazyTextField valueBinding="view.firstName" viewName="textField"}}' +
    '<input type="submit" value="Save" {{action save}}>' +
    '</form>'),
  save: function(e) {
    e.preventDefault(); e.stopPropagation();
    this.set('firstName', this.get('textField').getCurrentValue());
  }
});

Another pattern: One-way bindings

There is another way to achieve this, using the built-in live-updating Ember.TextField and a one-way binding. It’s more compact, though slightly more opaque:

1
2
3
App.LazyTextField = Ember.TextField.extend({
  valueBinding: Ember.Binding.oneWay('source')
});

Now instantiate LazyTextField, and bind your model data to source instead of value, and property changes will propagate only from source to value, but not vice versa (unless explicitly copied by the save function).

1
2
3
4
5
6
7
8
9
10
11
12
App.PersonView = Ember.View.extend({
  firstName: 'Jane',
  template: Ember.Handlebars.compile(
    '<form>' +
    '{{view App.LazyTextField sourceBinding="view.firstName" viewName="textField"}}' +
    '<input type="submit" value="Save" {{action save}}>' +
    '</form>'),
  save: function(e) {
    e.preventDefault(); e.stopPropagation();
    this.set('firstName', this.getPath('textField.value'));
  }
});