Forms angular comes with a small number of forms that can be used - sometimes with small amendments - to meet a vast number of requirements. They all use RESTful routes and handle querying and updating the server

For each model the following routes are supported:

  • /#/:model which lists the documents in the collection, with links to the form for editing them
  • /#/:model/new which enables the user to create a new document using the default form for the collection
  • /#/:model/:id/edit which enables the user to edit a document

Input Form

The form-input directive expands the schema into nice looking data capture form, but that is only a small part of the story. The basic edit form also gives you:

  • Header section showing the key fields from the record.
  • Buttons to perform the usual Save, Cancel, New and Delete operations. forms-angular handles all the back-end stuff for you.

    The form button customisation is currently limited to over-riding the default enabled state of the buttons by defining functions in a controller for the model (or model and form). See here for an example.

  • Error message section, which displays error messages when (for example) some server-side validation fails when updating something.

Listing Form

The listing routes (of the format /#/:model) are used to build a page containing a list of documents in the collection, showing the list fields. You can specify a sort order by adding a listOrder value to the model as shown in this model. Alternatively you can specify a sort order at run-time using the o parameter as in this example.

Listing routes support filters, such as

/#/b_using_options?f={"surname":"Smith"}

and calls to the aggregation framework, provided they project to an array of docs that contains an _id property which is used to select from the model. For example the (rather unpalatable)

/#/f_nested_schema?a=[{"$unwind":"$exams"},{"$sort":{"exams.score":1}},{"$group":{"_id":{"id":"$_id"},"bestSubject":{"$last":"$exams.subject"}}},{"$match":{"bestSubject":"English"}},{"$project":{"_id":"$_id.id"}}]

selects all students who did better in their English exam than any other subject. To find out how to use the aggregation framework refer to the MongoDB docs.

These can be combined (though there appears to be a problem unless the filter precedes the aggregation).

By default the list order is the MongoDB natural order. The default list order for a table can be set by specifying a listOrder option in the model definition (see g_conditional_fields for an example).

Form Styles

The formstyle attribute can take the values vertical, horizontal, inline and horizontalCompact. The first three generate the markup for a Twitter Bootstrap 2 style and the last slightly modified style. vertical generates markup that works with the default form style, and the others needs classes adding as follows:

formstyle Associated Bootstrap class(es)
horizontal form-horizontal
horizontalCompact form-horizontal compact
inline form-inline

Sub forms that are created by the directive will have these classes added in the appropriate place.

Client Side Customization

Additional functionality can be added by using "model controllers" which have the name of a model followed by Ctrl (or the name of the model followed by the name of the custom form followed by Ctrl. There is an sample model controller here which is used in the examples in this section. The NavCtrl controller handles the model controllers, so don't remove it.

Naming

The BaseCtrl scope has a variable called modelNameDisplay which is used in several places in the demo app. It defaults to the model name in title case, but can be over-ridden in the model controller.

Menu

The menu can be added to where required by the models (see an example here) The options can be configured to appear when records are being listed, edited or created. The top level text is taken from the model controller's dropDownDisplay variable, if present. If not present it will fall back to the modelNameDisplay (see above) and it that is not present the model name.

Post form-input generation processing

The form-input directive broadcasts a formInputDone message when it has processed a control. This can be acted on by the model controller. In our example we add a change handler to a select2 control which changes background color of a control group when the eye colour is changed.

Client side data events

There are hooks before and after CRUD events as follows:

  • onBeforeCreate function(data, callback(err))
  • onAfterCreate function(data)
  • onBeforeRead function(id, callback(err))
  • onAfterRead function(data)
  • onBeforeUpdate function(data, old, callback(err))
  • onAfterUpdate function(data, old)
  • onBeforeDelete function(old, callback(err))
  • onAfterDelete function(old)

In all onBefore... cases passing an error back will stop the event completing. There is a trivial example of how a data event hook might be used in this controller, which shows how such event handlers are set up.

You can also call onRecordChange function(data, old) which is useful for updating calculated fields etc.

Adding additional attributes to all elements.

It is possible to apply additional attributes to all elements of a certain type by passing it once in the form-input declaration. The available types are Control Group, Field or Label.

This can be achieved in two ways. Either as an attribute of the form-input element:

<form-input schema="formSchema" add-all-group="injected-element='with parameters'">

or via the controller by making it an attribute of scope:

$scope.addAllGroup="injected-attribute"

The three versions of this are:

  • add-all-group="injected-attribute"
  • add-all-field="injected-attribute"
  • add-all-label="injected-attribute"

For example if wished to inject a directive called 'hide-on-empty' to every individual control group then you would add:

add-all-group="hide-on-empty"

to the form-input declaration.

If declared in a controller then it will be applied to all child controllers. If declared in the form-input element the scope is limited to the individual form's scope. In this way a single declaration at the root scope is seem by all controllers.

Due to the parse method, in order declare multiple classes each class must be prefixed with 'class=' e.g.

<form-input schema="formSchema" add-all-group="injected-element='with parameters' class=myclass class=my-second-class">

Server Side Customization

Server side data events

On the server side there are hooks around data events as follows:

  • findFunc function(req, callback(err, query)) applies a filter to records returned by the server. A common use case is to restrict a user to only see their own records.
  • onSave function(doc, req, callback(err)) is a pre save hook that allows access to the record and the environment. A common use case is to apply 'fine-grain' authentication.

There are examples of both in this model