Introduction

The most visible part of forms-angular is the form-input directive which takes a schema in the scope and creates inputs. These schemas can be very simple as in the example below:

<div ng-init="name=[
    {id:'f_surname',label:'Surname',name:'surname',type:'text'},
    {id:'f_forename',label:'Forename',name:'forename',type:'text'}
    ]">
    <form>
        <form-input schema="name" />
    </form>
<div>

This simple bit of markup creates a small schema and stores it in the $scope as name(you wouldn't normally it like this, but it illustrates things well) then in a form invokes the form-input directive, telling it to use the schema just created.

Unfortunately displaying the resulting form stops the page scrolling to the internal links, so we have decided to omit it - there are plenty of examples accessible elsewhere on the site.

Mongoose Schemas

Mongoose is a popular package that simplifies using MongoDB with Node JS. Mongoose requires that a schema is created for each 'model'. As you will have seen in the Get Started section, forms-angular can convert Mongoose schemas into the schemas that the form-input directive uses, which saves a lot of coding by hand and makes it very easy to keep your front and back ends in sync.

As many models as you want can be registered with forms-angular by calling the addResource method of the forms-angular object as shown in the Get Started section. If you have more than a few models it is a good idea to put them in their own directory and loop through it.

A trivially simple schema looks like this:

var ApplicantSchema = new Schema({
    surname: {type:String, required:true, index:true},
    forename: {type:String, index:true},
});

Each schema element (or field) has a type and optionally a number of other properties. Many of these properties are used by forms-angular to generate appropriate behaviour on the front end. The appropriate input types are used, and client side validation keeps round trips down (we cannot say "to a minimum" as work remains to be done in this area).

You can get an idea of what forms-angular does with a vanilla Mongoose JS schema by looking at this schema and the form generated from it.

For comprehensive information about Mongoose schemas visit the Mongoose JS website.

List Fields

In a couple of places forms-angular uses the concept of 'list fields' - fields that generally allow the user to quickly see what they are dealing with - for example in the case of a person the forename and surname would be list fields. They don't have to be unique, they just have to be useful. List fields are used:

  • When populating select options when a model is referenced by another model
  • In list forms - where the contents of a collection are displayed
  • In search results (though this can be overridden)
  • In reports when a model is referenced by another model and a columnTranslation is used

A field may be specified to be a list field by adding a truthy value for a list key in the schema element: forename: {type:String, list:true}

List fields can be generated on both the front and back end, and it is done as follows:

  1. If there is at least one schema element with a truthy value for list then all such fields are list fields
  2. If no field is specified as a list field then:
    • On the client the first non hidden string field is used or failing that the first field
    • On the server the first two fields are used (one day there will hopefully be some consistency here!)

The form Object

The mark-up of generated forms can be influenced by use of the form object in the schema type: surname: {type:String, form:{label:'Family Name', size:'large'}}

The form object can have the following keys:

  • hidden inhibits this schema key from appearing on the generated form.
  • label overrides the default input label. label:null suppresses the label altogether.
  • placeHolder adds placeholder text to the input (depending on data type).
  • help adds help text under the input.
  • helpInline adds help to the right of the input.
  • size sets control width. Options are: mini, small, medium (default), large, xlarge, xxlarge and block-level.
  • directive allows you to specify custom behaviour.
  • readonly adds the readonly attribute to the generated input (doesn't work with date - and perhaps other types).
  • select2 in an enum field or a reference field tells the system to use the select2 control rather than a select. If the number of options is large in a reference field then select2:{fngAjax:true} instructs the program to use ajax calls to query the server rather than downloading the table. The values in the select2 control come from the listing fields.
  • rows sets the number of rows in inputs (such as textarea) that support this. Setting rows to "auto" makes the textarea expand to fit the content, rather than create a scrollbar.
  • pane used to divide a large form up into panes accessed by tabs (see example). This requires using the UI Bootstrap tabs module which is not in the minimal deployment folder (but is in this demo).
  • noAdd inhibits an Add button being generated for arrays.
  • noRemove inhibits a Remove button being generated for array elements.
  • add allows arbitrary attributes to be added to the input tag. Useful for adding classes.
  • formstyle (only valid on a sub schema) sets style of sub form. See form style section for options.
  • link sets up hyperlinks for reference fields as follows:
    • linkOnly if true (which at the time of writing is the only option supported) then the input element is not generated.
    • text the text used for the link.

This example schema and this form shows many of these options in use.

Containers

Sometimes it is hard within a Mongoose schema to detail exactly how you want the associated form or forms to be laid out. One particular class of layout features that currently cannot be requested within a Mongoose schema are containers. Hopefully this will be addressed in a forthcoming release but if you need them now then you need to create a schema by hand (or dynamically in code).

An example of a use of containers would be:

<form-input ng-init="names=[
  {containerType:'fieldset',title:'Name',content:
    [
      {id:'f_surname',label:'Surname',name:'surname',type:'text'},
      {id:'f_forename',label:'forename',name:'forename',type:'text'}
    ]
  }
]" schema="names"></form-input>

Other containers are tab, well, well-large and well-small.

If you want to "roll your own" you can do something like:

<style>
  .redBorder {border:solid 2px red;}
  p.bigRed {color: red; font-size: large}
</style>
<form-input ng-init="names=[
  {containerType:'redBorder',title:'Name',titleTagOrClass:'bigRed',content:
    [
      {id:'f_surname',label:'Surname',name:'surname',type:'text'},
      {id:'f_forename',label:'forename',name:'forename',type:'text'}
    ]
  }
]" schema="names"></form-input>

Where container type is seen as a class for a div and a titleTagOrClass of h1..h6 is interpreted as a heading tag and anything else as a class to be applied to a paragraph (as in the example). If a title is specified without a corresponding titleTagOrClass then <h4> is used as a default.

Custom Schemas

It is easy to create custom form schemas which are a subset of the whole schema by specifying the fields to include and any options. See the static in this example. The custom form schemas are invoked as follows:

Subkeys

Custom schemas are a good way to allow access to only part of a document, for instance if a customer record contained operational and accounts information an accounts user would require a different view of the data (that would include account balance) than a user from operations (who would not have access to the account balance). Consider the following:

var AddressSchema = new Schema({
    type: { type: String, default: 'Home', enum: ['Delivery', 'Invoice', 'Historic'] },
    street: String,
    town: String,
    postalCode: String
    }, {_id: false});

and in the customer model

addressList: {type: [AddressSchema], mergeKey: 'type', form: {pane: 'Address', labels: 'Addresses'}}

We need a way of having the delivery address appear on one form (for the operation user) and the invoice address on the finance form, even though they are in the same field.

forms-angular has the concept of a subkey for this scenario. The custom schema for the operations user would contain:

addressList: {subkey: {keyList: {type: 'Delivery'}, containerType: 'well-small', title: 'Address', titleTagOrClass: 'h5'}}

while that for finance might include

addressList: {subkey:
    [
        {keyList: {type: 'Invoice'}, containerType: 'well-small', title: 'Invoice To:'},
        {keyList: {type: 'Delivery'}, containerType: 'well-small', title: 'Deliver To:'}
    ]
}

Note that subkey can be an object or an array of objects. Where there are multiple sub docs that match the subkey the first will be used.