Editable Fields with Cancelability using Knockout

Update: this feature is now available as part of the ko.plus library available on GitHub and NuGet!


This post is a quick overview of a new feature added to my library of Knockout extensions & helpers which is available on GitHub and partially described here and here.

I quite often find myself wanting to write UI that is read-only until someone clicks on an Edit link, at which point it becomes editable and they can either save or cancel their changes.

A field that becomes editable when a link is clicked

This “edit-in-place” functionality is useful as you don’t need to change much UI and it isn’t too intrusive for quick changes (such as renaming things).

To achieve this behaviour using Knockout you would need the following on the view model:

  • A flag to indicate that the field is in Edit mode
  • A function to enter Edit mode
  • Two functions to exit Edit mode: one that confirms the change and one that rolls back to the original version

To save repeatedly adding the same boilerplate code I have written a simple helper function to encapsulate it: ko.editable.

Usage

To use the editable function, just replace any existing call to ko.observable() with ko.editable():

var ViewModel = function() {
  //equivalent to ko.observable("the title")
  this.field = ko.editable('the title');
};

You can still use the created field exactly as you would any other Knockout observable, but you have the option to bind to 4 new members to gain the editable functionality:

  • isEditing - an observable indicating whether or not the field is in edit mode
  • beginEdit - a function to enter edit mode that also saves the current value so that it can be restored when you cancel
  • cancelEdit - a function to exit edit mode and reset the value to whatever it was when beginEdit was called
  • endEdit - a function to exit edit mode and confirm any changes against the field

You can see a simple implementation here.

<input data-bind="value: field, enable: field.isEditing" />

<a href="#" data-bind="visible: !field.isEditing(), click: field.beginEdit"
  >Rename</a
>

<div data-bind="visible: field.isEditing">
  <a href="#" data-bind="click: field.endEdit">Confirm</a> |
  <a href="#" data-bind="click: field.cancelEdit">Cancel</a>
</div>

All we are doing is binding the value of an input element to the created field on the view model. We then have 3 links to begin, end and cancel edit mode, as well as a couple of flags to hide and disable elements as applicable.

This is obviously very basic, so I’ve created a slightly more in-depth (and prettier) example here.

Implementation

The implementation itself is relatively simple. We first create a Knockout observable to use as the base field - no point in re-writing anything when we can just re-use it.

ko.editable = Utils.editable = function(initial) {
  var _observable = ko.observable(initial);

  return _observable;
};

Next up, we add another observable to act as the isEditing flag…

_observable.isEditing = ko.observable(false);

…and then the 3 methods to enter and exit edit mode…

//start an edit
_observable.beginEdit = function() {
  _observable.isEditing(true);
};

//end (commit) an edit
_observable.endEdit = function() {
  _observable.isEditing(false);
};

//cancel an edit
_observable.cancelEdit = function() {
  _observable.isEditing(false);
};

…and that’s more-or-less it. Everything apart from rolling back the changes on Cancel, that is.

Rolling Back Changes on Cancel

So far we have a field that keeps track of it’s edit state, but we also need it to roll back the value when the user clicks Cancel.

To achieve this we can store the “current” value when the beginEdit function is called, and then re-set that against the base observable inside cancelEdit.

var _rollbackValue;

//...

_observable.beginEdit = function() {
  //store the current value
  _rollbackValue = _observable();
};

//...

_observable.cancelEdit = function() {
  if (!_observable.isEditing()) return;

  //re-set the original value
  _observable(_rollbackValue);

  _observable.isEditing(false);
};

Note that we are checking the isEditing flag before doing anything in the cancelEdit function. This is because we only want to be able to roll-back if we are currently editing - once an edit has been either confirmed or cancelled we no longer want to be able to revert to it’s old value.

The full source is available on GitHub (as are the unit tests).

Next

Whilst this is useful, it’s pretty basic and is only applicable to a few scenarios. A sensible progression of this idea is to make entire objects - not just single fields - editable in the same manner.

But that’s a topic for another day…