Back to top

Introduction 

Plaits is a form creation, validation and rendering library for Node.js.

It supports both synchronous and asynchronous validation (via Promises), and comes with many built-in validators, many of which are provided by the excellent validator.js library.

It is intended for use with Express, but can be used with other libraries and frameworks too.

Plaits is inspired by the form validation features found in frameworks such as .NET MVC, and thanks goes to the great work done on the Bookshelf ORM for additional inspiration and helping with some underlying code principles.

The source is available on GitHub, and it comes with a large suite of unit tests.

Latest Release - 0.1.10 

Version 0.1.10.

View the Changelog below.

Dependency & Test Status 

Build Status - Build Status

Dependencies - Dependency Status

Code Coverage - Coverage Status

Contribute 

Please feel free to report bugs and suggest features you’d like to see on the Plaits GitHub Issues Page, or send tweets to @persata.

Form Models 

Plaits models are simply Backbone models with different functionality.

A model must be given a name, and a list of fields. It also takes an optional list of validators (examples later).

Model Example 

A basic model example:

// Require Plaits
var Plaits = require('plaits');

// Register Form Declaration
var RegisterForm = Plaits.Model.extend(
  {
    // Form Name
    name: 'register_form',
    // Field List
    fields: [
      'username',
      'email_address',
      'password',
      'confirm_password'
    ],
    // Validators
    validators: {
      username: [
        Plaits.Validators.required(),
        Plaits.Validators.alphanumeric()
      ],
      email_address: [
        Plaits.Validators.required(),
        Plaits.Validators.email()
      ],
      password: [
        Plaits.Validators.required(),
        Plaits.Validators.minLength(8)
      ],
      confirm_password [
        Plaits.Validators.matchProperty('password')
      ]
    }
  }
);

// Export Form
module.exports = RegisterForm;

Model Field Labels 

Models can also take a labels object as part of their declaration:

// Register Form Declaration
var RegisterForm = Plaits.Model.extend(
  {
    ...
    labels: {
      'username': 'Your Chosen Username'
    }
    ...
  }
);

If a label is not provided, it will be auto generated by Node Inflection’s titleize function, for example:

email_address => 'Email Address'
confirm_password => 'Confirm Password'

This will return either the custom label you have set in the model itself, or will generate one.

Model Defaults 

You can specify the defaults for model values using the defaults key:

// Register Form Declaration
var RegisterForm = Plaits.Model.extend(
  {
    ...
    defaults: {
      'email_address': 'email@example.com'
    }
    ...
  }
);

Model Instantiation 

To instantiate a form model, simply require it (if necessary) & use new:

// Require
var RegisterForm = require('../forms/register');

// New Register Form
var registerForm = new RegisterForm();

getFieldIdentifier 

Gets the field identifier for a model’s field - this is used to generate a form name / ID that the parseRequest() functions can automatically parse.

It is made up of the field’s name and the model’s name joined with an underscore - see Form Request Parsing.

// Get Field Identifier
model.getFieldIdentifier(field);

// Examples
console.log(registerForm.getFieldIdentifier('email_address'));
// => 'register_form_email_address'

console.log(registerForm.getFieldIdentifier('username'));
// => 'register_form_username'

getLabelText 

Gets the label text for a model’s field. Used to fetch or generate the label text.

// Get Label for a Field
model.getLabelText(field);

// Examples
console.log(registerForm.getLabelText('email_address'));
// => 'Email Address'

console.log(registerForm.getLabelText('username'));
// => 'Username'

isRequired 

Checks the provided validators to see if the given field is required. This is achieved by analysing the function names for requiredValidator.

// Get Required Status
model.isRequired(field);

// Examples
console.log(registerForm.isRequired('email_address'));
// => true

addError 

Adds the specified error message to the specified field. Use if you want to manually add an error message for a field to the form.

// Add an Error
model.addError(field, errorMessage);

// Example
registerForm.addError('username', 'Manually adding this error message.');

console.log(registerForm.getFirstError('username'));
// => 'Manually adding this error message.'

hasErrors 

Check whether the specified field has any errors attached to it.

// Check For Errors
model.hasErrors(field);

// Example
console.log(registerForm.hasErrors('username'));
// => false

hasFile 

Check whether the specified field has a file uploaded to it - follows the same principles as file required validation, i.e. not undefined and size > 0.

// Check For File
model.hasFile(field);

// Example
console.log(registerForm.hasFile('avatar'));
// => true

Provided Validators 

Plaits comes with a large set of built in validators which should be able to handle many of your use cases. Many of the validators are backed by the validator.js library.

Many will take certain parameters to define their behaviour, for example minLength will take a parameter to define what the minimum length should be.

Custom Error Messages 

All built in validators take an optional custom error message if you wish to override the default error message.

You can use the string {{label}} in your custom error messages to have the label for the field automatically inserted, for example the string ‘{{label}} must be a minimum of 10 characters’ could produce ‘Your password must be a minimum of 10 characters’.

There are also other strings you can use depending on the validator, such as {{version}} for IP Address, {{minLength}} for minimum length etc. Each is described below.

Required 

Whether this field is required.

In most cases, this will check whether the value is null or not - null values are considered invalid.

When generating labels using the Html helpers and templates, the label text will be appended with an asterisk.

required([customErrorMessage])

Email 

Checks if the provided value is an email address.

email([customErrorMessage])

Minimum Length 

Checks that the value matches or exceeds the minimum length provided.

minLength(minLength, [customErrorMessage])

Maximum Length 

Checks that the value matches or is less than the maximum length provided.

maxLength(maxLength, [customErrorMessage])

Length 

This validator can be used to ensure an exact, minimum, or a range length of characters.

Ensure that if you are passing lengths that they are ints and not strings, so 6 instead of ‘6’.

Arguments

It takes between 1 and 3 arguments, as follows:

  • 1 Argument

    • An exact length for the string
  • 2 Arguments

    • Argument 1: A minimum OR exact length for the string
    • Argument 2: A maximum length for the string OR a custom error message
  • 3 Arguments

    • Argument 1: A minimum length for the string
    • Argument 2: A maximum length for the string
    • Argument 3: A custom error message
length(args...)

Numeric 

Ensures the value provided is a numeric value.

numeric([customErrorMessage])

Alpha 

Ensures the value provided only contains letters.

alpha([customErrorMessage])

Alphanumeric 

Ensures the value provided only contains letters and numbers.

alphanumeric([customErrorMessage])

Url 

This validator ensures that the value provided to the model is a valid URL.

Arguments

It takes between 0 and 2 arguments, as follows:

  • 0 Arguments:

    • Default options from below, default error message returned.
  • 1 Argument, either:

    • An object specifying the options to use for validation (see below), or
    • A custom error message
  • 2 Arguments

    • Argument 1: An object specifying the options to use for validation (see below)
    • Argument 2: A custom error message
Declaration
url(args...)
Default Validation Options
{
  protocols: ['http','https','ftp'],
  require_tld: true,
  require_protocol: false,
  allow_underscores: false,
  host_whitelist: false,
  host_blacklist: false
}

IP Address 

This validator ensures that the value provided to the model is a valid IP address. It can be used to validate version 4 or version 6 addresses, or can be version agnostic.

Arguments

It takes between 0 and 2 arguments, as follows:

  • 0 Arguments:

    • IP Address matching either version 4 or version 6, default error message returned.
  • 1 Argument, either:

    • An int (not a string!) that describes the version of IP address (4 or 6), or,
    • A custom error message
  • 2 Arguments

    • Argument 1: An int (not a string!) that describes the version of IP address (4 or 6)
    • Argument 2: A custom error message
ipAddress(args...)

Credit Card 

Ensures the value provided is a valid credit card number.

creditCard([customErrorMessage])

Equals 

When provided with an equalValue parameter, this will make sure the value provided to the form equals the equalValue provided.

equals(equalValue, [customErrorMessage])

One Of 

This validator should be passed an array of acceptable values, and if the value provided to the form for validation is one of those, it will pass validation.

oneOf(allowedValues, [customErrorMessage])

Contains 

This validator should be passed a string as the first parameter. If the value provided to the form for validation contains this value as a substring, it will pass validation.

contains(containsValue, [customErrorMessage])

Matches 

This validator should be passed a regex as the first parameter. If the value provided to the form for validation matches this regex, it will pass validation.

matches(regexValue, [customErrorMessage])

Date 

This validator will loosely check that the provided value for validation is a date. It can be of any format, even a Unix timestamp.

date([customErrorMessage])

Date with Specific Format 

This validator will check that the value provided to the form for validation is a valid date, and is in the format specified.

It should be provided a date format string as an argument.

This validator is backended by the moment.js library.

When specifying a custom error message, you may use {{dateFormat}} to have the specified format injected into your custom error message.

dateForm(dateFormat, [customErrorMessage])

Match Another Property / Field 

This validator takes a property key as the first parameter.

When validation runs, it will check that the value provided to the form for validation is the same as the value of the given property.

This is usually used for comparing password & confirm password fields (see the example above).

matchProperty(propertyKey, [customErrorMessage])

Int 

This validator will check that the value provided to the form is an integer.

int([customErrorMessage])

Float 

This validator will check that the value provided to the form is floating point number.

float([customErrorMessage])

Min 

This validator will check that the value provided to the form is greater than or equal to the minimum value given.

min(min, [customErrorMessage])

Max 

This validator will check that the value provided to the form is less than or equal to the maximum value given.

max(max, [customErrorMessage])

Range 

This validator will check that the value provided to the form is between or equal to the minimum and maximum values given.

range(min, max, [customErrorMessage])

File Validators 

Plaits allows for the validation of file uploads, with some setup (see File Field Mapping)

File Field Mapping 

Due to the various methods & libraries for handling file uploads in Node / Express, the resulting file fields on a file object differ depending on which library is being used.

For example, connect-multiparty will use originalFilename and type, whereas multer will have equivalent fields of originalname and mimetype.

In order to allow the validation of file uploads to use one consistent API, Plaits allows for the mapping of file fields to allow the validators to look up which fields to use.

In the case of Multer:

// Use Multer
app.use(multer({dest: '/tmp/uploads/'}));

// Set Multer Mappings
Plaits.setValidatorConfig({fileFieldMappings: Plaits.FileFieldMappings.Multer});

and for Multiparty:

// Use Multiparty
app.use(multipart());

// Set Multiparty Config
Plaits.setValidatorConfig({fileFieldMappings: Plaits.FileFieldMappings.Multiparty});

For custom file field mapping (in the case you’ve written the form handler yourself / are using another library), you can specify the mappings to suit. For example:

// Mappings
var fileFieldMappings = {
    originalFilename: 'original-file-name',
    type: 'file-mime-type',
    size: 'file-size'
};
// Set Mapping Config
Plaits.setValidatorConfig({fileFieldMappings: fileFieldMappings);

Required 

Whether this file field is required.

This will check if the uploaded file has a name and a size greater than 0 bytes.

When generating labels using the Html helpers and templates, the label text will be appended with an asterisk.

required([customErrorMessage])

Min Size 

This will check that the file being uploaded is greater than the minimum size provided to the function.

The minimum size value can given in either a numeric format, e.g. 60000 for bytes, or a string that follows JEDEC standards, e.g. 60kB, 0.5mb etc.

minSize(minSize, [customErrorMessage])

Max Size 

This will check that the file being uploaded is smaller than the maximum size provided to the function.

The maximum size value can given in either a numeric format, e.g. 60000 for bytes, or a string that follows JEDEC standards, e.g. 60kB, 0.5mb etc.

maxSize(maxSize, [customErrorMessage])

MIME-Types 

This will check that the file being uploaded is one of the MIME-Types provided to the function.

The function should be passed a string for a single MIME-Type, or an array of allowed MIME-Types.

The MIME-Type value given to the function can include wildcards if needed, for example ['image/*'], ['audio/*'], or can be an exact MIME-Type such as ['application/rtf'].

mimeTypes(mimeTypes, [customErrorMessage])

Enforce Mime Type Match 

Malicious users may attempt to upload files to your forms which are not what they seem - for example, uploading a file with extension ‘.jpg’ that is actually a ‘.zip’ file etc.

This validator uses mmmagic bindings to libmagic to inspect the uploaded file and ensure that the file’s MIME-Type actually matches its contents.

Due to the async nature of the libmagic bindings, this validator must be used with the validate() method on the form, as validateSync() will not suffice.

enforceMimeMatch([customErrorMessage])

Custom Validators 

Plaits provides the ability to define custom validation functions for your form models.

The returned result of the custom validation function will be cast to a promise, so the validation function can be synchronous or asynchronous depending on the requirements of the validation.

If the value provided is valid, then the overall function should return ‘true’, however if the value is invalid, a string should be returned explaining why the field is invalid.

Asynchronous use cases would be such things as checking a database to ensure a unique email address or username.

Example 

Custom validation functions should be defined as such:

/**
 * Define Your Unique Email Validator
 * @param fieldValue      - The value of the field
 * @param fieldLabel      - The label of the field
 * @param formModel       - The current instance of the form model this validator belongs to
 * @param additionalVars  - Additional vars passed to the validate() function (explained later)
 */
var uniqueEmailValidator = function(fieldValue, fieldLabel, formModel, additionalVars) {
  // Table
  var userTable = knexInstance('users');
  // Query
  return userTable.select('email').where('email', '=', fieldValue).then(function(result) {
      // Result Count
      if (result.length > 0) {
          // Return An Error
          return 'That email address is already in use!';
      } else {
          // Return True (Valid)
          return true;
      }
  });
};

// Add to Register Form Declaration
var RegisterForm = Plaits.Model.extend(
  {
    ...
    validators: {
      email_address: uniqueEmailValidator
    }
    ...
  }
);

Form Request Parsing 

If you are using the Express framework, you can have the module parse requests made to your routes.

This will analyse your GET and POST parameters, and assign the values to the form as they are found.

Values should be passed as (form name)_(field name), for example register_form_email_address, register_form_confirm_password and so on.

Example 

// Register Form
var RegisterForm = require('../forms/register');

// Test Route - POST
// Synchronous Parsing (Most common)
router.post('/test', function(req, res) {
    // New Form
    var registerForm = new RegisterForm();
    // Parse Request
    registerForm.parseRequestSync(req);
});

// Test Route - POST
// Asynchronous Parsing. Useful if you are using any related events that are also asynchronous.
// Unlikely but doesn't hurt to provide the option.
router.post('/test', function(req, res) {
    // New Form
    var registerForm = new RegisterForm();
    // Parse Request, Promise Based
    registerForm.parseRequest(req).then(function(formModel) {
      // ...
    });
});

Form Validation 

The main use of Plaits is to validate the data passed to the model and return feedback based on the validation result.

Validation can be done synchronously or asynchronously depending on the requirements of the form - if using all synchronous validators, then you can validate synchronously for simpler flow control. If there is a validator that needs to be asynchronous (such as checking a web service or querying a database), then you can validate asynchronously using Promises.

All current stock validators are synchronous, with the exception of the undocumented and yet unfinished File validators (see coming soon).

Validation 

Asynchronous Validation

To validate a model asynchronously, use the validate() method, which returns a Promise, eventually being fulfilled with the result of the validation.

Please note that the Promise is only rejected when something goes wrong with the validation - database connection timeout, exceptions etc. If the model is invalid (i.e. the end result is false), the promise is still fulfilled, with a false value. For a great explanation, see this cartoon.

// Validate
registerForm.validate().then(function (result) {
  // ...
});
Synchronous Validation

To validate a model asynchronously, use the validateSync() method, which returns the result of the validation:

// Validate
var result = registerForm.validateSync();

Passing Objects to Validators 

It is often the case that if you’re using a custom validator, you want to pass extra objects or information into the validation function (current user account etc).

This is possible with the additionalVars parameter on both the validate() and validateSync() methods, which will then pass the objects into the custom validation function:

Call Validate
// Current User
var currentUser = req.user;
// Validate With Additional Vars
var result = registerForm.validateSync(currentUser);
Inside The Function
// Custom Validator Function
var differentPassword = function(fieldValue, fieldLabel, formModel, currentUser) {
  // Do Things With 'currentUser'
};

Errors 

Validation would be useless if it were not able to display useful feedback to the user. When a field fails validation, the error is added to the internal _errors object.

You can get errors from the model with the following functions:

getErrors 

Returns the errors for all fields, or if passed a ‘field’ parameter, returns errors for only that field.

If there are no errors, returns an empty object.

model.getErrors([field])

getFirstError 

Returns the first error for the specified field.

If there are no errors for that field, returns an empty string.

model.getFirstError(field)

Errors Object 

The errors object is a key:array object that contains an entry for each field that has validation errors, for example:

{
  'username': [
    'Username must be at least 8 characters long.',
    'Username must consist of only letters and numbers.'
  ],
  'password': [
    'Password is a required field.'
  ]
}

Events 

Plaits models come with custom events that allow you to hook in and modify model behaviour or attributes when triggered.

Backbone Events 

Since Plaits models are essentially glorified Backbone models, you have access to all the underlying event functionality that Backbone provides. See the Backbone documentation for each of:

Plaits Specific Events 

Events use trigger-then, allowing for asynchronous behaviour in your event listeners if necessary. Ensure however, that if you are using asynchronous events that you use the asynchronous versions of parseRequest and validate.

Specify the events using on() in your initialize method like so:

// Register Form Declaration
var RegisterForm = Plaits.Model.extend(
  {
    ...
    initialize: function () {
      this.on('beforeParseRequest', function (model, request) {
        ...
      });
    },
    ...
  }
);

beforeParseRequest 

This even fires before a request is parsed, allowing you to modify the request or the model.

/**
   * Before Parse Event
   * @param model PlaitsModel - The form model
   * @param request Object - The express request object
   */
  this.on('beforeParseRequest', function (model, request) {
    ...
  });

afterParseRequest 

This even fires after a request is parsed.

/**
   * Before Parse Event
   * @param model PlaitsModel - The form model
   * @param request Object - The express request object
   */
  this.on('afterParseRequest', function (model, request) {
    ...
  });

beforeValidate 

This even fires before a model is validated.

/**
   * Before Validate Event
   * @param model PlaitsModel - The form model
   */
  this.on('beforeValidate', function (model) {
    ...
  });

afterValidate 

This even fires after a model has been validated.

/**
   * After Validate Event
   * @param model PlaitsModel - The form model
   * @param validationResult Boolean - Whether the model is valid or not
   * @param validationErrors Object - An object containing any validation errors
   */
  this.on('afterValidate', function (model, validationResult, validationErrors) {
    ...
  });

Express Middleware 

Plaits provides a middleware for Express that gives you the ability to render form fields automatically in your templates.

Plaits.ExpressMiddleware([options])

Options 

The middleware takes an options object, with the defaults:

{
  name: 'Plaits', // Helper Name for res.locals
  templatePaths: ['node_modules/plaits/lib/templates'], // Template Locations, see Html Templates
  templateExtension: 'jade', // Template Extension, see Html Templates
  renderSpecialAttributesValue: true, // Whether to Render Vals for Some Attributes (eg. muted)
  errorCssClass: 'error', // Error CSS Class On Generated Form Elements
  requiredCssClass: 'required', // Required CSS Class on Generated Form Elements
  errorContainerTag: 'div', // Container Wrapper Tag For Generated Error Output
  errorContainerClass: 'error-message', // Container Wrapper Class for firstErrorFor()
  errorSummaryContainerClass: 'error-summary', // Container Class for Generated Error Summary
  errorSummaryText: 'Please fix the following validation errors:' // Error Summary Text
}

See the below sections for further explanations on some of these options.

Html Helpers 

When using the Middleware specified above, it will add a helper to your res.locals which is then available in your templates.

You can specify the name of this helper by passing the option name as part of the Middleware, which defaults to Plaits.

You can also use multiple middleware if you need different behaviour by specifying different names for each, for example:

// Plaits Frontend Middleware
app.use(Plaits.ExpressMiddleware());

// Plaits Admin Middleware
app.use(Plaits.ExpressMiddleware({name: 'PlaitsAdmin', requiredCssClass: 'required-input'}));

Each of the following helpers is accessible under the Html property of your middleware item;

// Defaults
Plaits.Html.textFieldFor(...)
// Using 'PlaitsAdmin'
PlaitsAdmin.Html.textFieldFor(...)

button 

Renders a button.

button: function (label, [htmlAttributes])

resetButton 

Render a reset button.

resetButton: function (text, [htmlAttributes])

submitButton 

Render a submit button.

submitButton: function (text, [htmlAttributes])

labelFor 

Generates a label for a specified input.

If the field is required, it will contain <span>*</span> next to the text to show the required state. It will also have the requiredCssClass.

In the case that this field fails validation, it will also have errorCssClass added.

labelFor: function (model, attribute, [htmlAttributes])

firstErrorFor 

This will show the first error for the given model’s field/attribute.

The wrapping tag is specified by errorContainerTag (defaults to div) and the class will be errorContainerClass (defaults to error-message).

firstErrorFor: function (model, attribute, [htmlAttributes])

errorsFor 

Generates a container with all errors for the given model’s field/attribute.

The wrapping tag is specified by errorContainerTag (defaults to div) and the class will be errorSummaryContainerClass (defaults to error-summary).

errorsFor: function (model, attribute, [htmlAttributes])

errorSummary 

Generates an error summary with all errors for the entire model.

Takes an optional summaryText parameter, which will be displayed at the top of the summary.

This defaults to ‘Please fix the following validation errors’, or whatever you have passed into the initial Middleware options.

errorSummary: function (model, [summaryText], [htmlAttributes])

textFieldFor 

Render a text input for the given model’s attribute.

textFieldFor: function (model, attribute, [htmlAttributes])

emailFieldFor 

Render an email input for the given model’s attribute.

emailFieldFor: function (model, attribute, [htmlAttributes])

textAreaFor 

Render a text area for the given model’s attribute.

textAreaFor: function (model, attribute, [htmlAttributes])

numberFieldFor 

Render a number input for the given model’s attribute.

numberFieldFor: function (model, attribute, [htmlAttributes])

rangeFieldFor 

Render a range input for the given model’s attribute.

rangeFieldFor: function (model, attribute, [htmlAttributes])

colorFieldFor 

Render a color input for the given model’s attribute.

colorFieldFor: function (model, attribute, [htmlAttributes])

searchFieldFor 

Render a search input for the given model’s attribute.

searchFieldFor: function (model, attribute, [htmlAttributes])

dateFieldFor 

Render a date input for the given model’s attribute.

dateFieldFor: function (model, attribute, [htmlAttributes])

timeFieldFor 

Render a time input for the given model’s attribute.

timeFieldFor: function (model, attribute, [htmlAttributes])

telFieldFor 

Render a tel input for the given model’s attribute.

telFieldFor: function (model, attribute, [htmlAttributes])

urlFieldFor 

Render a url input for the given model’s attribute.

urlFieldFor: function (model, attribute, [htmlAttributes])

hiddenFieldFor 

Render a hidden input for the given model’s attribute.

hiddenFieldFor: function (model, attribute, [htmlAttributes])

passwordFieldFor 

Render a password input for the given model’s attribute.

passwordFieldFor: function (model, attribute, [htmlAttributes])

fileFieldFor 

Render a file input for the given model’s attribute.

fileFieldFor: function (model, attribute, [htmlAttributes])

selectFor 

Render a select field for the given model’s attribute.

Takes an items parameter, which should be a key:value object specifying which items should be the options.

selectFor: function (model, attribute, items, [htmlAttributes])

checkboxFor 

Render a checkbox input for the given model’s attribute.

checkboxFor: function (model, attribute, [htmlAttributes])

radioFor 

Render a radio input for the given model’s attribute.

radioFor: function (model, attribute, [htmlAttributes])

checkboxListFor 

Render multiple checkbox fields for the given model’s attribute.

Takes an items parameter, which should be a key:value object specifying which items should be individually rendered.

checkboxListFor: function (model, attribute, items, [htmlAttributes])

radioListFor 

Render multiple radio fields for the given model’s attribute.

Takes an items parameter, which should be a key:value object specifying which items should be individually rendered.

radioListFor: function (model, attribute, items, [htmlAttributes])

Html Templates 

In addition to the Html Helpers specified above, Plaits also comes with a set of ready-built Html templates that are wrappers for the above helpers.

The Html templates are built with Jade.

Each of the field helpers specified above has a corresponding template which contains a label, a field of the specified type, and will render any validation errors from that field.

The template functions are named the same as their helpers, only without the word ‘For’ in the function name, so email(), text() etc.

Location 

If you wish the view the default templates, they are in lib/templates.

Usage 

The templates are accessible inside your templates via your middleware, like so:

// Default 'Plaits'
Plaits.Html.Template.text(...)
Plaits.Html.Template.email(...)
Plaits.Html.Template.select(...)

// Different Name Specified For Middleware, e.g. 'PlaitsAdmin'
PlaitsAdmin.Html.Template.textArea(...)
PlaitsAdmin.Html.Template.number(...)
PlaitsAdmin.Html.Template.password(...)

Custom Html Templates 

The Plaits templates can be customised in a few different ways.

When specifying your middleware options for Plaits, it is possible to pass a custom templatePaths option.

Plaits templates are cached at render time for performance, so you will need to restart your application server when changing the templates.

This option can be a single path, or an array, of locations where Plaits should look for form html templates:

// Resolve Path
var myTemplatePath: path.join(__dirname, 'views', 'forms');
// Use Middleware
app.use(Plaits.ExpressMiddleware({templatePaths: [myTemplatePath]));

Overriding Defaults 

Simply copy the templates you wish to override from lib/templates to your new location - in the above example views/forms.

When Plaits scans the directories for the requested template, e.g. the email template, it will prioritise the custom location over the default location, and will render the one residing in views/forms.

Entirely Custom Templates 

Since you may wish to have multiple versions of an template, e.g. different classes or wrappers, the Plaits middleware provides the following function:

/**
 * Render a Custom Template.
 * Takes template name, model, attribute and any additional parameters to pass to the template.
 * @param templateName string
 * @param model PlaitsModel
 * @param attribute string
 * @param additionalParams Object
 * @return {*}
 */
render: function (templateName, model, attribute, [additionalParams])

This function will look for the template with templateName and will render that template if found.

Use the additionalParams parameter to pass any other parameters required for the rendering of the template, e.g. a list of items for a select input.

While templates can have customisable file extensions (via the templateExtension option passed to your middleware), the templates MUST be in the Jade template language. While this is more opinionated that is pleasant, Jade is at least very well supported, documented and tested.

Full Example 

Below is a full (although very simple) end-to-end example of creating, rendering and validating a Plaits model using Express.

Middleware 

Express app.js, before routes:

// Require Plaits
var Plaits = require('plaits');

// Plaits Middleware
app.use(Plaits.ExpressMiddleware());

Model 

Model Declaration, living in forms/register.js:

// Require Plaits
var Plaits = require('plaits');

// Register Form Declaration
var RegisterForm = Plaits.Model.extend(
  {
    // Form Name
    name: 'register_form',
    // Field List
    fields: [
      'username',
      'email_address',
      'password',
      'confirm_password'
    ],
    // Validators
    validators: {
      username: [
        Plaits.Validators.required(),
        Plaits.Validators.alphanumeric()
      ],
      email_address: [
        Plaits.Validators.required(),
        Plaits.Validators.email()
      ],
      password: [
        Plaits.Validators.required(),
        Plaits.Validators.minLength(8)
      ],
      confirm_password [
        Plaits.Validators.matchProperty('password')
      ]
    }
  }
);

// Export Form
module.exports = RegisterForm;

Routes 

Route file, routes/account.js:

// Require Form
var RegisterForm = require('../forms/register');

// Account Register Route - GET
router.get('/account/register', function (req, res) {
    // New Form
    var registerForm = new RegisterForm();
    // Render Template, Passing Form
    res.render('account/register', {
        registerForm: registerForm
    });
});

// Account Register Route - POST
router.post('/account/register', function (req, res) {
    // New Form
    var registerForm = new RegisterForm();
    // Parse Request & Validate
    registerForm.parseRequestSync(req).validate().then(function (result) {
        // Valid?
        if (result) {
            // Do Register / Database Stuff Here
            // ...
            // Redirect
            res.redirect('/account/register/success');
        } else {
            // Invalid, Re-Render The Template
            res.render('account/register', {
                registerForm: registerForm
            });
        }
    }).catch(function (e) {
        // Something Went Horribly Wrong, Caught A Rejected Promise
        res.json(e);
    });
});

Template 

Register template, account/register.jade:

extends layout

block content
    form(method='POST')
        !=Plaits.Html.Template.text(registerForm, 'username')
        !=Plaits.Html.Template.email(registerForm, 'email')
        !=Plaits.Html.Template.password(registerForm, 'password')
        !=Plaits.Html.Template.password(registerForm, 'confirm_password')
        !=Plaits.Html.Template.submit()

And that is enough to render the form, handle the POST request, validate the form and display any validation errors to the user.

Coming Soon 

Here are a few things I plan on incorporating or working on the future.

More Validators 

Extra provided validators are always useful to prevent people from having to write their own, especially if tested and approved, for example:

  • Date Range Validator (Dates between X and Y, dates only before Y, etc.)

Examples Repository 

A GitHub repository with some examples of using Plaits in different circumstances would be useful.

Changelog 

0.1.10
  • Fixed issue with radio input value comparisons incorrectly comparing types

  • Updated various dependencies

0.1.9
  • Fixed an issue with selected values on select fields where the ‘multiple’ attribute was set
0.1.8
  • Fixed an issue with escaping text on certain form elements
0.1.7
  • Fixed an issue with attempted MIME type checking when nothing was uploaded
0.1.6
  • Fixed an issue with the templates for multiple Express middleware instances getting mixed up
0.1.5
  • Added new function, model.hasFile(field), to easily check if a file has been submitted as part of the form request

  • Fixed issue with Plaits template function calls interfering with Express res.locals

  • Fixed issue with checkbox value comparisons not always comparing variable types properly

0.1.4
  • Fixed issue with labels returning undefined

  • Updated various dependencies

0.1.3
  • Fixed issue with mime match validator and file field mapping
0.1.2
  • Added file validation support (required, minSize, maxSize, mimeTypes & enforceMimeMatch - see File Validators)

  • Added support for mapping file field values to their respective fields (see File Field Mapping)

  • Updated various dependencies (Namely lodash to v3)

0.1.1
0.1.0 - Initial Release
  • Initial Release

Generated by aglio on 18 Jan 2016