Displaying error messages in your Ember.js forms

Handling errors in forms

In an ideal world, all form fields in a web application would be filled in with valid values at all times. However, this is not an ideal world. Users make typo’s, omit fields by accident, or accidentally press “Enter” before having finished filling in a form. In this post, I will explain how you can easily display error messages in your Ember.js forms without too much added work.

Things on the back-end

When dealing with erroneous user input, it’s up to you, the web developer, to make sure that you leave your database in a consistent state at all times. Therefore, you have to ensure that only user input that is valid according to your business logic ever gets persisted. Luckily, the Active Record layer in Rails has validations which allow you to do just that. Furthermore, through the ActiveModel::Errors class, you can easily return the right messages according to the error.

JSON representation of errors

In an Ember.js application, your front-end gets its data from the back-end through JSON responses. So you have to find a suitable representation for your errors. There are several conventions here, for instance Google has one, Ember Data works out of the box in the format described here, but in the end it depends on the information that you want to include in your error responses. In the Hstry application, I decided to go for the following structure:

{
  errors: [
    {
      field: "username",
      messages: ["This username does not exist"]
    },
    {
      field: "password",
      messages: ["You can't leave the password blank"]
    }
  ]
}

So we can represent any number of error messages for each field in a form. There are other representations that add more information, like an error code for instance, but we don’t need that information at this stage.

To have Rails controllers return responses for your models in this format, you have to create a custom responder. Place the following content in a file such as custom_responder.rb in /lib.

class CustomResponder < ActionController::Responder
  def json_resource_errors
    {
      errors: resource.errors.keys.map{ |attribute| {
                field: attribute,
                messages: resource.errors[attribute]
               } }
    }
  end
end

Then in ApplicationController, add the line self.responder = CustomResponder.

Parsing the JSON structure

Let's say that we submit the form from within the controller for our page in the following way:

App.LoginController = Ember.Controller.extend({
  ...
  actions: {
    login: function() {
      // `this` refers to the controller we're currently in
      Ember.$.ajax({
        type: "POST", 
        url: theLoginUrl,
        data: someFormData,
        dataType: "json"
      }).done(function(responseData) {
        Ember.run(function() {
          // Deal with the case everything is fine
        });
      }).fail(function(responseData) {
        Ember.run(this, function() {
          // Everything is not fine, this is where we're interested in
          this.set('requestMessages',
                   App.RequestMessagesObject.create({
                     json: responseData.responseJSON
                   })
          );
        });
      });
    });
  }
});

This App.RequestMessagesObject object will parse the JSON structure for errors and will set the errors messages for each field in the response. An example will make it clearer so after applying this to the JSON structure above, we will get the following result:

// `this` refers to the controller
this.get('requestMessages.username') == ["This username does not exist"];
this.get('requestMessages.password') == ["You can't leave the password blank"]

The object that does this is defined as follows.

/* Converts a JSON error response for a request into an Ember error object. */
App.RequestMessagesObject = Ember.Object.extend({
  /* Must provide the following argument at creation:
   * - json
   */
  // It's bad practice to override the `init` function for reasons
  // explained in http://reefpoints.dockyard.com/2014/04/28/dont-override-init.html
  _doInitialization: function() {
    var self = this;

    this.get('json')["errors"].forEach(function(error_obj) {
      self.set(error_obj.field, error_obj.messages);
    });
  }.on('init')
});

Display the error messages in your Ember.js form

The requestMessages property is an Ember Object so we can display it in a template. Imagine we have the following template for our login form:

<form {{action "login" on="submit}}>
  <div class="row">
    <label>Username</label>
    {{input type="text" value=username}}
  </div>
  <div class="row">
    <label>Password</label>
    {{input type="password" value=password}}
  </div>
</form>

We can then simply display the error messages as follows:

<form {{action "login" on="submit}}>
  <div class="row">
    <label>Username</label>
    {{input type="text" value=username}}
    {{#each error in requestMessages.username}}
      <span class="message error">{{error}}</span>
    {{/each}}
  </div>
  <div class="row">
    <label>Password</label>
    {{input type="password" value=password}}
    {{#each error in requestMessages.password}}
      <span class="message error">{{error}}</span>
    {{/each}}
  </div>
</form>

It's fairly obvious that this can be DRY'ed up and extracted into an Ember.js Component. But I'll leave that as an exercise to the reader :-).

Write us your thoughts about this post. Be kind & Play nice.