AngularJS and Rails Tutorial: part 7 form error handling, datepicker

Part 7 of a tutorial that connects AngularJS to a Rails backend. This post focuses on better error handling, adding a datepicker to the modal, and generally tidying up the application, making it prettier and removing some of the sample app that came with ng-boilerplate so that this is more a League application.  The previous post was Creating teams and a relationship between clubs and teams, the next post is not written yet (although there are some ancillary items such as CSRF and Devise integration on the index page).  You can also find the index of the posts in the tutorial, or hit the tutorials menu at the top and select the Rails 3 tutorial.

There is a newer, rails 4 and newer angularJS, version of this tutorial.  It is also more complete and has a nicer UI that doesn’t use modal windows, which is probably a better choice for anyone starting fresh today.  The first page in that tutorial is here, and the index here.

If you haven’t completed the previous pages in this tutorial the code to date can be downloaded from github: tutorial 6.

In this section of the tutorial we’re going to tidyup the look and feel.  This includes getting rid of a lot of the things that came in from the boilerplate that we don’t need, and tidying up the styling of the application somewhat.  When finished your app should look something like:

Starting with the tidyup, edit src/app/about/about.tpl.html to have appropriate content.  Really you can say anything you like here, or not much at all, but get rid of the boilerplate stuff.

<div class="row-fluid">
  <h1 class="page-header">
    League Application
    <small>A sample sports league.</small>
  </h1>
  <p>
    The league application is sample application that demonstrates one way
    to integrate rails and angularjs. It makes a conscious choice to
    separate the rails backend and all the testing and build goodness that goes
    with that from the angularjs front-end, and the build tools and flexibility that
    comes with that.
  </p>

  <h2>Why?</h2>

  <p>
    I've been working on the infrastructure for a rails app, and we've recently made
    the choice to move to angularjs as a front end. I saw a number of gems that seek
    to integrate angularjs directly into the Rails asset pipeline and testing tools,
    but ultimately I feel like that will give only a fraction of the goodness that the
    angular ecosystem can offer, whereas separating in this way will potentially
    give better access to the build and test tools, the widgets and the extensions
    that the angularjs community have to offer.
  </p>

  <p>
    This sample app is based around the <code>ng-boilerplate</code> scaffolding, which
    provides a lot of the build scripts and tools for angularjs.
  </p>
</div>

Next, src/app/home/home.tpl.html.  The top block we tidy up to be more League like and cut out most of the content:

<div class="jumbotron">
  <h1>League application</h1>

  <p class="lead">
  A sample angularjs and rails application
  </p>

</div>

Then, src/app/index.html. In here delete the “Read the docs” button, and anything else that you don’t like the look of.

Delete the content relating to the plusOne button – this means:

  •  The plusOne link in the index.html page
  • The plusOne directory in src/common
  • The plusOne inclusion in src/app/home/home.js

This should give you an application that looks like this:

Tutorial_7 home page

Tutorial_7 about page

If you didn’t want to do that tidyup, the code for this point can be found in github:tutorial_7a (except I appear to have included the subsequent devise work into that commit as well, so I need to sort that out).

We’d like to tidy up our error handling somewhat, and we’d like to modify app/models/team.rb to make both name and captain mandatory (mostly so that we have errors to see):

class Team < ActiveRecord::Base
  belongs_to :club
  attr_accessible :captain, :date_created, :name, :club_id
  validates :name, :captain, presence: true
end

Instead of having our errors all displayed at the top of the page, we’d like the errors that are tied to an individual field to be displayed against that field.  To this end, we’re going to modify src/app/team/team_edit.tpl.html to display errors against each field.  Rails returns an error block that is in the general format {"name":["can't be blank"],"captain":["can't be blank"]} – so a hash of field names, and against each field name we get an array of error messages.  We can therefore tie these to the individual fields as follows:

<div class="modal-body">
  <div ng-show="error" class="error">
    <p class="error">Team could not be saved</p>
  </div>
  <div ng-class="{error: error.data.name}">Name: <input ng-model="team.name" />
    <div ng-show="error.data.name">
      <div ng-repeat="field_error in error.data.name">{{field_error}}</div>
    </div>
  </div>
  <div ng-class="{error: error.data.captain}">Captain: <input ng-model="team.captain" />
    <div ng-show="error.data.captain">
      <div ng-repeat="field_error in error.data.captain">{{field_error}}</div>
    </div>
  </div>
  <div ng-class="{error: error.data.club_id}">Club: <select ng-model="team.club_id" ng-options="club.id as club.name for club in clubs"></select>
    <div ng-show="error.data.club_id">
      <div ng-repeat="field_error in error.data.club_id">{{field_error}}</div>
    </div>
  </div>
</div>

What this is doing is setting the error class for the whole div if there is something in the error class for that field ("ng-class="{error: error.data.name}"), which will make everything red.  It also shows an element if there are errors for the field (ng-show="error.data.name"), and in that element it iterates over the array of errors for the field and prints out each of those errors.

We also need to create an error class in the stylesheet, we’ll do this in src/app/less/main.less:

.error {
  color: red;
}

This should give you error handling like the below.  It would be elegant to left indent the errors for each field so they’re under the text box, and align the text boxes, but we haven’t done that as yet.

Tutorial_7 teams error handling

Whilst this works, we don’t particularly like having to repeat this block at the bottom of each and every field we put on the page.  We can fix this by writing a directive – which allows us to create a reusable block of HTML, in effect extending the HTML language to include custom components.

Our aim is to be able to write

    <div>
      <label class="control-label">Name:</label>
      <div class="controls">        
        <input type="text" ng-model="team.name" />
      </div>
      <div error_display field="name" errors="error.data"></div>
    </div>

against each of our fields, and have the field_error directive automatically insert the error for us.

Create a new folder src/common/error_handling, and inside it a new file src/common/error_handling/error_handling.tpl.html.  We’ll do the formatting for our directive first in this html, we want it to look like:

  <div ng-show="errors[field]" class="form-horizontal control-group">
    <div class="controls">
      <div ng-repeat="error in errors[field]">error: {{error}}</div>
    </div>
  </div>

This is the same repeat format, but using variables that will be passed to the partial for the field that we are displaying, and for the errors block.

We then need to define a directive to use this.  Directives seem a bit fussy, one key thing to remember is that your directive declaration must create your directive in camelCase (e.g. errorDisplay), but when you use it in your html you always use it with underscores (e.g. error_display).  Why?  No idea, but I couldn’t make it work without.

The directive code will go in a new file src/common/error_handling/error_handling.js:

/**
 * Common error handling module
 */

angular.module( 'common.error_handling', [])

.directive('errorDisplay', function () {
  return {
    restrict: 'A',
    scope: {
      errors: '=',
      field: '@field'
    },
    templateUrl: 'error_handling/error_handling.tpl.html'
  };  
})
;

What this does is to declare a directive called errorDisplay, this directive should be restricted in it’s use to being an attribute within html.  So this means we use it on a normal html directive, and add an attribute of error_display.  So, for example, <div error_display>.

This directive will create a controller and a scope behind the scenes, this scope is isolated from the controller already on our teams page.  In short, this means that each instance of this directive has it’s own scope and variables.  Those variables will be errors, which we expect to be passed in to us, but which we expect to be a model attribute in our parent controller (the ‘=’ tells the directive to expect to find that attribute on the parent controller).  Finally, we have a field that will be passed to us, and that field should be local (we don’t want to share it with other instances of this same directive – as each should have a different field).

You also need to add common.error_handling to the dependencies on your app.js.  Save all this, and try modifying your team page to use this new directive:

<div class="modal-header">
  <h3>Edit Team</h3>
</div>

<div class="modal-body">
  <div ng-show="error" class="error">
    <p class="error">Team could not be saved</p>
  </div>
  <div>Name: <input ng-model="team.name" />
    <div error_display field="name" errors="error.data"></div>
  </div>
  <div>Captain: <input ng-model="team.captain" />
    <div error_display field="captain" errors="error.data"></div>
  </div>
  <div>Club: <select ng-model="team.club_id" ng-options="club.id as club.name for club in clubs"></select>
    <div error_display field="club_id" errors="error.data"></div>
  </div>
</div>

<div class="modal-footer">
  <button ng-click="submit()" class="btn btn-primary" >Save</button>
  <button ng-click="cancel()" class="btn btn-primary" >Cancel</button>
</div>

The content up to this point is available on github as tutorial 7b.

Next, we’re going to add a date picker for the create date, the documentation and example/sample code for which we can find with the angular-ui-bootstrap project. We need a newer version of angular-bootstrap for this to work, so we’re going to:

  bower install angular-bootstrap

This will ask you what version you want, select 0.5.0 or newer, if it gives you the option use an ! at the start to save this resolution then do so.

In the current version (angular 1.2.0-rc2 and angular-bootstrap 0.5.0) the date picker will come up, but has no dates shown on it. In 1.2.0-rc1 it works, and there’s a defect outstanding to fix it that is probably going to be fixed in 1.2.0-rc3. When I upgrade angular-bootstrap to 0.6.0, I get an error on dialog provider, it looks like $dialog turned into $modal, but the calling syntax has also changed a fair bit.

Then, in teams.js that are needed:

  $scope.dt = new Date();

And change team_edit.tpl.html to have some datepicker goodness:

  <div class="form-horizontal" ng-class="{error: error.data.create_date}">
    Create Date: <input type="text" datepicker-popup="dd-MMMM-yyyy" ng-model="team.create_date" open="opened" />
    <div ng-show="error.data.club_id">
      <div ng-repeat="field_error in error.data.club_id">{{field_error}}</div>
    </div>
  </div>

There are other configuration options, you can find those by reviewing the documentation mentioned above. Note that as of current writing, the option of having a button that opens the datepicker isn’t working – but it looks like it will be fixed in 0.6.0.

On my chrome browser this scrolls off the bottom of the modal, so we need a way to resize the modal when the popup appears (or for the popup to go beyond the modal).  There appear to be ways to resize the modal, but they are a little excessively tricky at the moment.  I’m being lazy and adding white space to team_edit.tpl.html:

<div style="height:300px">&nbsp</div>

More to come as I get there, in the meantime you might want to consider looking at the Devise authentication integration or read the CSRF page, both of which are also mentioned on the index (which may have more stuff on it by the time you read this).

Advertisements

6 thoughts on “AngularJS and Rails Tutorial: part 7 form error handling, datepicker

  1. Pingback: CRUD application with AngularJS, Rails, twitter-bootstrap and ng-boilerplate: part 6 another entity | technpol

  2. Pingback: AngularJS and Rails: Tutorial Index | technpol

  3. Bro, your figures on NZ superannuation were way out. Just because you have Abott PM now doesn’t mean you can make up figures , get over to Kiwiiblog check out Lolitasbrother comment . Normally you sharp as an angular JS dude not this time though

  4. Pingback: Adding translation using angular-translate to an angularjs app | technpol

  5. Pingback: AngularJS and Rails CRUD application using ng-boilerplate and twitter bootstrap | Gatelockservice

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s