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=[{name:'surname'},{name:'forename']">
    <form-input schema="name" />
<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 invokes the form-input directive, telling it to use the schema just created to create a form with the appropriate input fields.

Record content: {{record}}

There is more information about the forms-input directive on the forms page.

Form Schemas

The form-input directive can get a good deal richer than the simple example above. The following options are available in a form schema element:

  • type sets the type of input control generated. In addition to the standard HTML5 types there are some 'special' types:
    • textarea a textarea control
    • radio a radio button control
    • select a select control
  • id specifies the id of the input field (which defaults to f_name)
  • label specifies the label of the input field (which defaults to name converted to title case)
  • required validates the field
  • placeHolder allows placeholder text to be specified
  • showWhen either:
    1. an object of the form {lhs:variable, comp:comparison, rhs:variable} where:
      • variable can be a string, number or field in the model preceded by $
      • comparison is one of 'eq', 'ne', 'gt', 'gte', 'lt', 'lte'

      or

    2. an angular expression
  • help is for specifying help text
  • helpInline is for specifying help text which is displayed inline
  • add allows anything else to be added to the input html. For example "add": "ui-date ui-date-format " (note the space at the end - add always needs this) brings up the angular-ui date picker.
  • rows (in conjunction with a textarea type) allows the depth of the control to be specified. The auto option creates a textarea that automatically expands
  • options (in conjunction with a select or radio type) allows the options for the select / radio button group to be specified. You can either do this by passing the option values in an array and passing it directly, or by putting them in an array on the scope and passing the name of the array (which allows run-time modification
Example
<div ng-init="example2=[
        {name:'surname',label:'Family Name',required:true},
        {name:'forename',id:'fnameinput'},
        {name:'dateOfBirth',type:'date'},
        {name:'sex',type:'radio',options:['male','female']},
        {name:'hairColour',type:'select',options:['brown','black','blonde','grey']}]">
    <form-input schema="example2" />
<div>

which looks like:

Record content: {{exampledata}}

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 (specifically the BaseCtrl controller) 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. If you use BaseCtrl, then you should set the schema attribute of the form-input tag to "baseSchema()".

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 (for instance an enumstring) attribute will generate a select control (you can specify radio button - see below) 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 optional keys:

  • type the input type to be generated - which must be compatible with the Mongoose type. Common examples are email, url. Note that if the field type is String and the name (or label) contains the string 'password' then type="password" will be used unless type="text". If the Mongoose schema has an enum array you can specify a radio button group (instead of a select) by using a type of radio
  • .
  • 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.
  • popup adds popup help as specified.
  • order allows user to specify the order / tab order of this field in the form. This overrides the position in the Mongoose schema.
  • size sets control width. Options are: mini, small, medium (default), large, xlarge, xxlarge and block-level.
  • 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.
  • tab used to divide a large form up into a tabset with multiple tabs (see example).
  • showWhen allows conditional display of fields based on values elsewhere. For example having prompted whether someone is a smoker you may want a field asking how many they smoke a day:
    smoker: {type: Boolean},
    howManyPerDay: {type: Number, form:{showWhen:{lhs:"$smoker", comp:"eq", rhs:true}}}

    As you can see from the example there are three parts to the showIf object:

    • lhs (left hand side) a value to be compared. To use the current value of another field in the document preceed it with $.
    • comp supported comparators are 'eq' for equality, 'ne' for not equals, 'gt' (greater than), 'gte' (greater than or equal to), 'lt' (less than) and 'lte' (less than or equal to)
    • rhs (right hand side) the other value to be compared. Details as for lhs.
  • 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.
  • inlineRadio (only valid when type is radio) should be set to true to present all radio button options in a single line
  • editor (only valid when type is textarea) should be set to ckEditor to use CKEditor
  • directive allows you to specify custom behaviour. Gets passed attributes from form-input (with schema replaced with the current element - so add can be used to pass data into directives).
  • customSubDoc (only valid on a sub document) allows you to specify custom HTML (which may include directives) for the sub doc
  • customFooter (only valid on a sub document) allows you to specify custom HTML (which may include directives) for the footer of a group of sub docs
  • 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.

If you need even more flexibility then you can create a function that returns an object of the form {before: markup before container contents, after:markup after container contents}.

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.