Thursday, October 6, 2011

Highlighting Validation Failures in Backbone.js

‹prev | My Chain | next›

Last night I was able to build up a Rails-like errors class for encapsulating Backbone.js errors. In the end, I was still pulling out the full error messages to report back like this:
Tonight, I hope to be able to highlight the offending fields.

The Errors class so far boast the following interface:
      var Errors = function(options) {...};

      _.extend(Errors.prototype, {
        isEmpty: function() {...},
        add: function(attribute, error) {...},
        each: function(callback) {...},
        full_messages: function() {...}
      });
So I ought to be able to use the each method to iteration over each field with an error (along with the error message). The HTML in the edit dialog looks like:
<div id="edit-dialog" title="Edit calendar appointment">
  <h2 class="startDate"></h2>
  <div style="display: none" class="errors"></div>

  <p>
    <label>title<br/>
      <input type="text" name="title"/>
    </label>
  </p>

  <p>
    <label>description<br/>
      <input type="text" name="description"/>
    </label>
  </p>
</div>
(the values of date, title, and description are populated by my Backbone view)

In the edit-appointment Backbone view (a singleton view because it re-uses a single jQuery UI dialog), the OK button's click event is bound to the update() method:
         var AppointmentEdit = new (Backbone.View.extend({
          // ....
          events : {
            'click .ok': 'update'
          },
          update: function() {
            var attributes = { /* pull from the dialog input fields */};

            var options = { /* ... */ };

            this.model.save(attributes, options);
          }
        }));
The update() method tells the model to save itself. Success or failure of that save is handled by callbacks inside the options object literal passed to save:
        var AppointmentEdit = new (Backbone.View.extend({
          // ...
          update: function() {
            var attributes = { /* pull from the dialog input fields */ };

            var options = {
              // close the dialog if save was successful
              success: function() { $('#edit-dialog').dialog("close"); },

              // draw error messages in the dialog if there was a validation failure
              error: function(model, errors) {
                // display full messages at the top of the dialog
                var full_messages = errors.full_messages();
                $('.errors', '#edit-dialog').
                  html(full_messages.join("<br/>")).
                  show();
                });
              }
            };

            this.model.save(attributes, options);
          }
        }));
So far, I am only adding the full error messages. To highlight the individual fields with errors, I iterate over each error from my Errors object. For each, I find the <label> tag that has an input with a name that is same as the field with the error message. If the title field has an error, then I find the <label> containing the title input field. For each such label, I add the "error" class to it (which makes the background bright yellow). Lastly, I wrap each input field inside that <label> with an "error" class <div>:
        var AppointmentEdit = new (Backbone.View.extend({
          // ...
          update: function() {
            var attributes = { /* ... */ };

            var options = {
              //....
              error: function(model, errors) {
                // ...
                errors.each(function(attribute, message) {
                  $('label', '#edit-dialog').
                    has('input[name=' + attribute + ']').
                    addClass('error').
                    find('input').
                    wrap('<div class="error"/>');
                });
              }
            };

            this.model.save(attributes, options);
          }
        }));
And that works:
Yay!

The only deficiency with this approach is when I fix one field, only to introduce an error in another:
I can address that with:
        var AppointmentEdit = new (Backbone.View.extend({
          // ...
         update: function() {
            $('.error').removeClass('error');
            //...
         }
       });
I think that about does it for my validations exploration. Unless I can think of something else to explore here, I will likely move on to a different Backbone topic tomorrow.


Day #155

No comments:

Post a Comment