Build Forms in AngularJS From Nothing But JSON
Uses the MIT License. See LICENSE
file for details.
- Clone the project from either GitHub or BitBucket - whichever you prefer.
- Copy
dynamic-forms.js
into your project wherever your other assets reside.
As with any other AngularJS module:
- include the script into your page anywhere after AngularJS itself.
<script src="assets/js/dynamic-forms.js"></script>
- INTERNET EXPLORER: This project (as with most of Angular itself) WILL NOT work
properly with IE 6 or 7. Some of the functionality can be coerced into working, but much of it
will simply be broken.
radio
fields, for example, will have every member selected. This may be fixed in a future version, but as it's 2014, IE 6 and 7 are very low priorities, especially with XP reaching end of life. IE 8 will work, with a bit of extra setup (you can try this for IE 6 and 7 as well, but again, they probably won't work):
<!--[if lte IE 8]>
<script src="https://cdnjs.cloudflare.com/ajax/libs/json3/3.3.1/json3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/2.3.0/es5-shim.min.js"></script>
<script>
document.createElement('ng-include');
document.createElement('ng-pluralize');
document.createElement('ng-view');
document.createElement('ng-form');
document.createElement('dynamic-form');
// Optionally these for CSS
document.createElement('ng:include');
document.createElement('ng:pluralize');
document.createElement('ng:view');
document.createElement('ng:form');
// IE doesn't always run the bootstrap on its own...
$(document).ready(function() {
angular.bootstrap(document);
});
</script>
<![endif]-->
- list
dynform
as a dependency of your project.
appModule = angular.module('app', ['dynform']);
- create a dynamic-form element anywhere in your page.
<dynamic-form template="formTemplate"
ng-model="formData"
ng-submit="processForm()">
</dynamic-form>
- populate your template with a JSON object describing the form you want to create.
$scope.formTemplate = {
"first": {
"type": "text",
"label": "First Name"
},
"last": {
"type": "text",
"label": "Last Name"
},
"submit": {
"type": "submit"
},
};
And that's about it!
You invoke the dynamic-form
directive using an element (<dynamic-form></dynamic-form>
) - other
options (such as class, attribute, and comment) are unsupported (for now). The directive requires
two attributes: an ng-model
, and either a template
or a template-url
. The ng-model
will be used to generate valid ng-model
attributes for the various input controls in the
template. In accordance with how AngularJS handles this attribute elsewhere, your entire form's
data will be available in keys of whichever model you specify here (though nested forms are an
exception, unless you specify a key in the outer form's model as the ng-model
of the inner
form).
If you specify a template-url
, the dynamic-form
directive will retrieve the form template via
$http
and build out your form based on the response. Currently, failure is silently ignored.
This may change in a later release.
You may not want to rely on the directive to retrieve your form directly - perhaps you want to do
some processing on the server response before passing it to the directive for building, or maybe
you need to specify a more complex $http
request with advanced authentication. Or perhaps you
just want to proactively handle failure to retrieve the template. Enter the template
attribute.
When the directive sees template
, it ignores any template-url
and instead uses the object
identified by the template
attribute.
Any other attributes you specify on the dynamic-form
element are copied across to the form
or
ng-form
element that the directive builds to replace itself with. Similarly, any pre-existing
contents are copied across as well, to the top of the resulting form, with the
dynamically-specified controls below them. This allows you to nest dynamic-form
s inside each
other in the same way as ng-form
(which is one reason this directive implements this
pseudo-transclusion).
The dynamic-form
directive makes every attempt to set up the forms it generates to be valid HTML
forms, complete with the ability to have their data submitted to the server by the browser's native
form submission mechanism and still have the data in the same structure that it takes on in your
AngularJS models. This makes it easy to implement a fallback mode in case there is a problem
with using the standard Angular methods to handle your form inputs. You will, of
course, need to provide your own action
and method
attributes for this to work completely.
Regardless of whether it arrives via template
or template-url
, the form template is a
fairly-straightforward JavaScript object. Its keys correspond (by default) to input control
name
s, which in turn (also by default) correspond to keys in the form's model. Each of the values
of this object is another object describing a form input control. A type
key identifies what kind
of control to build, which in turn determines what other keys are expected. Any
type
that isn't supported builds a <span>
containing the value of the label
key, if one
exists, and any other keys as attributes. Following is a list of all currently-supported type
s,
and then a more detailed specification of each. (Note that not all of these are properly supported
in all browsers, yet; there are a number of references around the web for which browsers support
what)
- button
- checkbox
- checklist
- color
- date
- datetime
- datetime-local
- fieldset
- file
- hidden
- image
- legend
- month
- number
- password
- radio
- range
- reset
- search
- select
- submit
- tel
- text
- textarea
- time
- url
- week
attributes
: key-value pairs for arbitrary attributes not otherwise supported here; it is strongly recommended that you use this option only if the attribute you need isn't already supportedclass
: seeng-class
callback
: seeng-change
(orng-click
for button-like types)disabled
: seeng-disabled
label
: wraps the control in a<label>
tag with the value as text content (but see specific types for exceptions to how this is handled)- The following options are only supported for types that have values:
model
: overrides the control's ID as the value ofng-model
andname
attributes; allows multiple controls to be tied to a single modelreadonly
: seeng-readonly
required
: seeng-required
val
: an initial value for the model
- Renders:
<button></button>
- Additional Options:
- None
- Other Notes:
- The value of
label
is used as the content of the<button>
itself; no additional elements are created
- The value of
- Renders:
<input type="checkbox">
- Additional Options:
isOn
: seeng-true-value
isOff
: seeng-false-value
slaveTo
: seeng-checked
- Other Notes:
- See also the checklist type, below
- Renders: multiple
<input type="checkbox">
controls - Additional Options:
options
: an object containing a collection of child objects, each describing a checkbox- The key of each child object specifies the key to associate with the checkbox it describes
class
: applies a specificng-class
to the current checkbox, independently of the restlabel
: operates identically to the standardlabel
option, but applies to a specific checkbox in the list- See the checkbox type for other fields supported here
- Other Notes:
- This is a convenience type, used to tie a group of checkbox controls together under a single model; the model holds an object, and each control sets a separate key within it
- You can set a
val
on the entirechecklist
(it must, of course, be an object) in addition to any per-optionval
s; the per-option versions are set after the fullchecklist
version, so they will override anything set to their key by thechecklist
itself
- Renders:
<input type="color">
- Additional Options:
- None
- Other Notes:
- May not be supported in all browsers
- Renders:
<input type="date">
- Additional Options:
- See text below
- Other Notes:
- May not be supported in all browsers
- Renders:
<input type="datetime">
- Additional Options:
- See text below
- Other Notes:
- May not be supported in all browsers
- Renders:
<input type="datetime-local">
- Additional Options:
- See text below
- Other Notes:
- May not be supported in all browsers
- Renders:
<input type="email">
- Additional Options:
- See text below
- Other Notes:
- On devices that have on-screen keyboards, the browser may modify the keyboard layout to make entering email addresses in these controls easier.
- Renders:
<fieldset></fieldset>
- Additional Options:
fields
: the template for the fields which should appear in thefieldset
- Other Notes:
- The value of
label
is used to create a<legend>
tag as the first child of thefieldset
- The value of
- Renders:
<input type="file">
- Additional Options:
multiple
: whether or not the user can select more than one file at a time with this single control
- Other Notes:
- A directive is included with this module that allows
file
controls to properly bind to AngularJS models - the control's FileList object is stored in the model, and updating the model's value with a valid FileList object will update the control accordingly - Also included is an AngularJS service that wraps the browser's FileReader in a promise, so you can get the contents of the selected file for further manipulation, or even send it along in an AJAX request, and all without leaving AngularJS
- Both of these additions are modified versions of code by K. Scott Allen and made available on the OdeToCode website; the original versions are linked above
- A directive is included with this module that allows
hidden
- Renders:
<input type="hidden">
- Additional Options:
- None
- Other Notes:
- Because the underlying HTML control has so little functionality, this control only supports
model
andval
keys
- Because the underlying HTML control has so little functionality, this control only supports
- Renders:
<input type="image">
- Additional Options:
source
: the URL of the image to display in this control
- Other Notes:
- The value of
label
is used to set thealt
attribute of this control
- The value of
- Renders:
<legend></legend>
- Additional Options:
- None
- Other Notes:
- As a display-only control, only
class
,label
,callback
(viang-click
) anddisabled
are supported on this control - The value of
label
is used to set the contents of this control
- As a display-only control, only
- Renders:
<input type="month">
- Additional Options:
- See text below
- Other Notes:
- May not be supported in all browsers
- Renders:
<input type="number">
- Additional Options:
maxValue
: the largest allowed value for this controlminValue
: the smallest allowed value for this controlstep
: the amount by which the control can increase or decrease in value- Also see text below
- Other Notes:
- May not be supported in all browsers
- Renders:
<input type="password">
- Additional Options:
- See text below
- Other Notes:
- The only real difference between this control and a text control is in the rendering,
so they support exactly the same options (with the exception of
splitBy
, since it makes no sense to split obscured-input strings)
- The only real difference between this control and a text control is in the rendering,
so they support exactly the same options (with the exception of
- Renders: multiple
<input type="radio">
controls - Additional Options:
values
: an object which acts as a simple list of radio options to include- The key of each property of this option specifies the value the model should be set to
when the associated radio
input
is selected - The value of each property of this option specifies the label to use for the associated
radio
input
- The key of each property of this option specifies the value the model should be set to
when the associated radio
- Other Notes:
- Because a single radio
input
by itself isn't particularly useful in most cases, this control type assumes users will want to define a list ofvalue:label
pairs tied to a single model; if this is incorrect, you can still createradio
controls with just onevalue:label
each, and then tie them together using themodel
key - The directive doesn't prevent you from applying a
label
to the entire collection ofinput
controls created by this control type - the entirediv
containing them will be wrapped in a<label>
tag; keep this in mind when building style sheets
- Because a single radio
- Renders:
<input type="range">
- Additional Options:
step
: the amount by which the control can increase or decrease in value- Also see number above
- Other Notes:
- By default, this control seems to provide its values to AngularJS as strings. This might
be due to Angular (as well as the browser) handling them as regular
text controls internally. Among its other minor tweaks, this module contains a
very simple directive to override the default
$parsers
mechanism forrange
controls and convert these values back to numbers (floats, in case yourstep
is not an integer). - May not be supported in all browsers
- By default, this control seems to provide its values to AngularJS as strings. This might
be due to Angular (as well as the browser) handling them as regular
text controls internally. Among its other minor tweaks, this module contains a
very simple directive to override the default
- Renders:
<button type="reset"></button>
- Additional Options:
- None
- Other Notes:
- As with button, the value of
label
provides the control's contents - AngularJS doesn't seem to monitor the
reset
event, so your models wouldn't normally be updated when the form is cleared in this way; while this control is strongly dis-recommended in most cases, this directive supports it, so code is included that monitors and properly handles these events (NOTE - this feature has not been widely tested; please report any issues on GitHub or Bitbucket)
- As with button, the value of
- Renders:
<input type="search">
- Additional Options:
- See text below
- Other Notes:
- All browsers support this because it works exactly like a text control. The idea is that search boxes will be styled differently, and on some devices might even support additional input methods, such as voice recognition. You'll probably want to tie these controls to some kind of search mechanism in your app, since users whose browsers do render them differently will expect them to act accordingly.
- Renders:
<select></select>
- Additional Options:
autoOptions
: seeng-options
empty
: if notfalse
or undefined, specifies the display value (contents) of an emptyoption
, and tells the directive to include it in its outputmultiple
: if notfalse
or undefined, allows the user to select more than oneoption
at one timeoptions
: an object containing a collection of child objects, each describing anoption
to include in theselect
list- The key of each child object gives the value of the associated
option
disabled
: seeng-disabled
group
: adds theoption
to anoptgroup
whose label is the value of thegroup
keylabel
: the display value (contents) of theoption
slaveTo
: seeng-selected
- The key of each child object gives the value of the associated
- Other Notes:
- An unreleased prototype version of this module (which used a combination of
ng-repeat
andng-switch
instead of a directive) specified four different control types for the functionality provided by this one - one for normal lists, one for grouped lists, one for multi-select lists, and one that combined multi-select with group support; this version is much cleaner about its approach - multi-select is the 'flag' optionmultiple
, and groups are enabled by simply defining them with their associated values - Note that only one of
options
andautoOptions
will be honored by this module - if you specifyautoOptions
,options
is completely ignored; keep this in mind when building your forms because the actual values will have to be specified in a separate object
- An unreleased prototype version of this module (which used a combination of
- Renders:
<button type="submit"></button>
- Additional Options:
- None
- Other Notes:
- As with button, the value of
label
provides the control's contents
- As with button, the value of
- Renders:
<input type="tel">
- Additional Options:
- See text below
- Other Notes:
- There is currently no validation support for this control in nearly any browser, and AngularJS doesn't provide any by default. This is mostly because telephone numbers are tricky beasts, especially with the differences between how these numbers are allocated from one part of the world to another. And that's before we start getting messy with things like VoIP "numbers".
- Renders:
<input type="text">
- Additional Options:
maxLength
: seeng-maxlength
minLength
: seeng-minlength
placeholder
: a value to display in atext
-like control when it is emptysplitBy
: seeng-list
(this option is only supported bytext
and textarea controls)validate
: seeng-pattern
- Other Notes:
- This control serves as the base template for nearly all the other form input controls defined by HTML - as such, most of the controls supported by this directive support these options as well, leading their entries to refer here
- Renders:
<textarea></textarea>
- Additional Options:
- See text above
- Other Notes:
- While the syntax used to define them in raw HTML differs to some extent, the only practical
difference between text and
textarea
controls is the multi-line support offered bytextarea
- therefore, the options available to each are identical
- While the syntax used to define them in raw HTML differs to some extent, the only practical
difference between text and
- Renders:
<input type="time">
- Additional Options:
- See text above
- Other Notes:
- May not be supported in all browsers
- Renders:
<input type="url">
- Additional Options:
- See text above
- Other Notes:
- Renders:
<input type="week">
- Additional Options:
- See text above
- Other Notes:
- May not be supported in all browsers
- Frank Linehan for leading me in the right direction.
- K. Scott Allen for the file input directive and the FileReader service adapted for use here.
- Joel Hooks for pointing out that the LGPL is too strong for this kind of project, and that a monolithic piece of code like version 0.0.0 makes very little sense in an MVVM environment.
If you notice a problem, let me know about it on GitHub or Bitbucket!
Any and all help is welcome; just fork the project on either GitHub or BitBucket (whichever you prefer), and submit a pull request with your contribution(s)!