AngularJS xtForm
v0.1beta

The xt-form directive allows for a better form validation experience when using AngularJS. By simply adding the required declarative attributes to the form we receive tooltip support and full form validation on demand.

Features: Requires:

Basic Bootstrap Form

Using the default form snippet from bootstrap we can simply add xt-form to the form element, and xt-validate to the input/select elements to get tooltip validation.

Show me the code...
<form class="form-horizontal" role="form" xt-form novalidate>
  <div class="form-group">
    <label for="u1" class="col-sm-2 control-label">Username</label>
    <div class="col-sm-10">
      <input name="username" type="text" xt-validate class="form-control" id="u1" placeholder="Username" ng-model="model1.username" required>
    </div>
  </div>
 <div class="form-group">
    <label for="email1" class="col-sm-2 control-label">Email</label>
    <div class="col-sm-10">
      <input name="email" type="email" ng-model="model1.email" xt-validate  class="form-control" id="email1" placeholder="Email">
    </div>
  </div>
  <div class="form-group">
    <div class="col-sm-offset-2 col-sm-10">
      <button type="submit" class="btn btn-default">Sign in</button>
    </div>
  </div>
</form>

Minimalist Example

The simplest usage of xt-form

<form xt-form novalidate>
  <input name="email" ng-model="modelVal" xt-validate required>
  <button type="submit">Submit</button>
</form>

Custom Error Messages

Default error messages are set on the provider, which can be overriden during module configuration.

Custom error messages can be added to an input by using msg-* properties. For example to override the required message use msg-required="This is a new message". These properties can also be interpolated i.e. msg-required="{ { message } }" allowing for a custom directive to dynamically update the error message if need be.

Show me the code...
<form ng-init="message='this is a dynamic message'" class="form-horizontal" role="form" xt-form novalidate>
    <div class="form-group">
      <label for="u3" class="col-sm-2 control-label">Static</label>
      <div class="col-sm-10">
        <input name="username1" type="text" xt-validate class="form-control" id="u3" placeholder="This example shows a static message on required" required ng-model="model3.username1" msg-required="This is a custom message">
      </div>
    </div>
    <div class="form-group">
      <label for="u4" class="col-sm-2 control-label">Dynamic</label>
      <div class="col-sm-10">
        <input name="username2" type="text" xt-validate class="form-control" id="u4" placeholder="This example shows a dynamic message on required" ng-model="model3.username2" required msg-required="{{message}}">
      </div>
    </div>
    <div class="form-group">
      <div class="col-sm-offset-2 col-sm-10">
        <button type="submit" class="btn btn-default">Sign in</button>
      </div>
    </div>
  </form>

Profiles

By Default tooltips are hidden until the input is focused or hovered over. Changing profiles allows this behaviour to be defined declaratively on the form. Click submit below to see the 'showAll' behaviour.

Show me the code...
<form profile='showAll' ng-init="message='this is a dynamic message'" class="form-horizontal" role="form" xt-form novalidate>
    <div class="form-group">
      <label for="u5" class="col-sm-2 control-label">Static</label>
      <div class="col-sm-10">
        <input name="username1" type="text" xt-validate class="form-control" id="u5" placeholder="This example shows a static message on required" placement="top" required ng-model="model5.username1" msg-required="This is a custom message">
      </div>
    </div>
    <div class="form-group">
      <label for="u6" class="col-sm-2 control-label">Dynamic</label>
      <div class="col-sm-10">
        <input name="username2" type="text" xt-validate class="form-control" id="u6" placeholder="This example shows a dynamic message on required" ng-model="model6.username2" required msg-required="{{message}}">
      </div>
    </div>
    <div class="form-group">
      <div class="col-sm-offset-2 col-sm-10">
        <button type="submit" class="btn btn-default">Submit</button>
      </div>
    </div>
  </form>

Full Example

This example shows a comprehensive form with html5 validation attributes, custom directive validation and the controller code to make it work.

Required field, showing custom error message
Required field, but should be no longer than 5 characters
Must be a correct email address
Required field.
If specified, must be between 2 and 20
This shows a custom directive. If specified, must be exactly the word 'hello'
  module.controller('pageCtrl', function ($scope) {
    $scope.form = {
      onSubmit: function (isValid) {
        if (isValid) {
          alert('do post...');
        }
      }
    };
  });
<form name="tester" class="form-horizontal" role="form" xt-form="form" novalidate>
  <div class="form-group">
    <label for="username10" class="col-sm-2 control-label">Username</label>
    <div class="col-sm-10">
      <input name="username" type="text" xt-validate class="form-control" id="username10" placeholder="Username" ng-model="model10.username" required msg-required="custom error message">
      <span class="help-block">Required field, showing custom error message</span>
    </div>
  </div>
  <div class="form-group">
      <label for="choice10" class="col-sm-2 control-label">Choice</label>
      <div class="col-sm-10">
        <select id="choice10" class="form-control" name="option" required xt-validate ng-model="model10.option">
          <option>test 1</option>
          <option>test 2</option>
          <option>test 3</option>
        </select>
      </div>
    </div>
  <div class="form-group">
    <label for="nick10" class="col-sm-2 control-label">Nickname</label>
    <div class="col-sm-10">
      <input name="nickname" type="text" xt-validate class="form-control" id="nick10" placeholder="Nickname" ng-model="model10.nickname" required ng-maxlength="5">
      <span class="help-block">Required field, but should be no longer than 5 characters</span>
    </div>
  </div>
 <div class="form-group">
    <label for="email10" class="col-sm-2 control-label">Email</label>
    <div class="col-sm-10">
      <input name="email" type="email" ng-model="model10.email" xt-validate  class="form-control" id="email10" placeholder="Email">
      <span class="help-block">Must be a correct email address</span>
    </div>
  </div>
  <div class="form-group">
    <label for="password10" class="col-sm-2 control-label">Password</label>
    <div class="col-sm-10">
      <input name="password" type="password" ng-model="model10.password" xt-validate  class="form-control" id="password10" placeholder="Password" required>
      <span class="help-block">Required field.</span>
    </div>
  </div>
  <div class="form-group">
    <label for="number10" class="col-sm-2 control-label">Number</label>
    <div class="col-sm-10">
      <input name="number" type="number" xt-validate ng-model="model10.number" class="form-control" id="number10" placeholder="Number" min="2" max="20">
      <span class="help-block">If specified, must be between 2 and 20</span>
    </div>
  </div>
  <div class="form-group">
    <label for="exactWord" class="col-sm-2 control-label">Must be the word 'hello'</label>
    <div class="col-sm-10">
      <input name="exactWord10" type="text" xt-validate class="form-control" id="exactWord10" placeholder="Must be the word 'hello'" ng-model="model10.exactWord" msg-exactword="Must be the word 'hello'" test-exactword="hello">
      <span class="help-block">This shows a custom directive. If specified, must be exactly the word 'hello'</span>
    </div>
  </div>
  <div class="form-group">
    <div class="col-sm-offset-2 col-sm-10">
      <button type="button" ng-click="reset()" class="btn btn-default">Reset</button>
      <button type="submit" class="btn btn-default">Submit</button>
    </div>
  </div>
</form>
  .xt-error {
    border: 1px solid red;
    color: red;
  }

  .xt-error:focus {
    border: 1px solid red;
    box-shadow: 0 0 2px red;
  }

Events & Control

The xt-form attribute can take a parameter of type FormControl which is specified below

var FormControl = {

  // event handlers
  onSubmit: function (isValid) {
    // do stuff when isValid
  },

  // methods attached by xt-form on load
  validate: function () {..} // to be called by page controller,
  reset: function () {..} // to be called by page controller,
  submit: function () {..} // to be called by page controller
};
      

This means that when a form is created like so <form xt-form="formOptions"> we can specify what to do when the form has passed validation by using the onSubmit event handler function on the formOptions object. We can also call $scope.formOptions.submit(); from the page controller to force a submit event (which in turn will raise the onSubmit handler).