Build Forms in AngularJS From Nothing But JSON
Uses the MIT License. See LICENSE
file for details.
The easy way.
bower install angular-dynforms
(add --save
if you want to add the dependency to your own
project - HIGHLY RECOMMENDED)The old way.
dynamic-forms.js
into your project wherever your other assets reside.When registering this project with bower, I discovered that there's another project called angular-dynamic-forms already registered there. The project was created at the beginning of October 2014, long after this one, and I haven't yet worked out if there are any similarities in the implementation, but as I've been thinking of shortening the name of this project for a while anyway, I went ahead and registered it in bower with the shorter name. I'll be changing the repo name on GitHub and BitBucket, too, but not for several months, to give existing users time to notice the addition of full bower support. The repo will be renamed to match the name registered in bower, and the bower name will not change. It is strongly recommended to use the bower method so you can get the latest version of this project at any given time, regardless of whether I've gotten around to renaming the repo.
As with any other AngularJS module:
<script src="https://github.com/danhunsaker/angular-dynamic-forms/raw/master/bower_components/angular-dynforms/dynamic-forms.js"></script>
require('angular-dynforms');
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.documentElement);
});
</script>
<![endif]-->
dynform
as a dependency of your project. appModule = angular.module('app', ['dynform']);
<dynamic-form template="formTemplate"
ng-model="formData"
ng-submit="processForm()">
</dynamic-form>
$scope.formData = {}; // JavaScript needs an object to put our form's models into.
$scope.formTemplate = [
{
"type": "text",
"label": "First Name",
"model": "name.first"
},
{
"type": "text",
"label": "Last Name",
"model": "name.last"
},
{
"type": "email",
"label": "Email Address",
"model": "email"
},
{
"type": "submit",
"model": "submit"
},
];
$scope.processForm = function () {
/* Handle the form submission... */
};
And that's about it! Check out the demo for a more robust example, or keep reading to learn about all of the things you can do with this module.
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). You must initialize this parent model to an object, or your app will break.
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 array
identified by the template
attribute. (See below for more details on this value.)
At some point in the future you will also be able to dynamically update this array, and the changes
will automatically be reflected in the DOM. This is currently unsupported, however, and for technical
reasons, will likely not be supported at all for templateUrl
arrays.
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 array/object. Each index/key of the template value (referred to
elsewhere in this README as an ID) serves as the name
and ng-model
(where applicable) of the
control described in the corresponding value. Each of the values, then, is an 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, as its content, and any other keys as
attributes.
Following is a list of all currently-supported type
s, and then a more detailed specification of
each. Links to Angular documentation in the specifications below indicate that values will be
added to the Angular-defined attributes mentioned, and that Angular provides the actual
functionality described there. Note that not all of these type
s are properly supported in all
browsers, yet; there are a number of references around the web for which browsers support
what.
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 supported, as
any attributes specified here bypass any enhancements this module provides.
class
: see ng-class
disabled
: see ng-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 of ng-model
and name
attributes;
allows multiple controls to be tied to a single model - you can nest your models further
by using dot notation in the valuereadonly
: see ng-readonly
required
: see ng-required
val
: an initial value for the model<button></button>
label
is used as the content of the <button>
itself; no additional elements
are created<input type="checkbox">
isOn
: see ng-true-value
isOff
: see ng-false-value
slaveTo
: see ng-checked
<input type="checkbox">
controlsoptions
: an object containing a collection of child objects, each describing a checkbox
class
: applies a specific ng-class
to the current checkbox, independently of the
restlabel
: operates identically to the standard label
option, but applies to a specific
checkbox in the listval
on the entire checklist
(it must, of course, be an object) in addition
to any per-option val
s; the per-option versions are set after the full checklist
version, so they will override anything set to their key by the checklist
itself<input type="color">
<input type="date">
<input type="datetime">
<input type="datetime-local">
<input type="email">
<fieldset></fieldset>
fields
: the template for the fields which should appear in the fieldset
label
is used to create a <legend>
tag as the first child of the fieldset
<input type="file">
multiple
: whether or not the user can select more than one file at a time with this single
controlfile
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<input type="hidden">
model
and val
keys<input type="image">
source
: the URL of the image to display in this controllabel
is used to set the alt
attribute of this control<legend></legend>
class
, label
, callback
(via ng-click
) and
disabled
are supported on this controllabel
is used to set the contents of this control<input type="month">
<input type="number">
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<input type="password">
splitBy
, since it makes no
sense to split obscured-input strings)<input type="radio">
controlsvalues
: an object which acts as a simple list of radio options to include
input
is selectedinput
input
by itself isn't particularly useful in most cases, this control
type assumes users will want to define a list of value:label
pairs tied to a single model;
if this is incorrect, you can still create radio
controls with just one value:label
each, and then tie them together using the model
keylabel
to the entire
collection of input
controls created by this control type - the entire div
containing
them will be wrapped in a <label>
tag; keep this in mind when building style sheets<input type="range">
step
: the amount by which the control can increase or decrease in value$parsers
mechanism for range
controls
and convert these values back to numbers (floats, in case your step
is not an integer).<button type="reset"></button>
label
provides the control's contentsreset
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)<input type="search">
<select></select>
autoOptions
: see ng-options
empty
: if not false
or undefined, specifies the display value (contents) of an empty
option
, and tells the directive to include it in its outputmultiple
: if not false
or undefined, allows the user to select more than one option
at
one timeoptions
: an object containing a collection of child objects, each describing an option
to
include in the select
list
option
disabled
: see ng-disabled
group
: adds the option
to an optgroup
whose label is the value of the group
keylabel
: the display value (contents) of the option
slaveTo
: see ng-selected
ng-repeat
and ng-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' option multiple
, and
groups are enabled by simply defining them with their associated valuesoptions
and autoOptions
will be honored by this module - if you
specify autoOptions
, 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<button type="submit"></button>
label
provides the control's contents<input type="tel">
<input type="text">
maxLength
: see ng-maxlength
minLength
: see ng-minlength
placeholder
: a value to display in a text
-like control when it is emptysplitBy
: see ng-list
(this option is only supported by text
and
textarea controls)validate
: see ng-pattern
<textarea></textarea>
textarea
controls is the multi-line support offered
by textarea
- therefore, the options available to each are identical<input type="time">
<input type="url">
<input type="week">
If this project isn't for you (it's not very mature, yet, so there are plenty of reasons it may not be a good fit for your projects, yet), there are some other ways to go about the same basic thing. They each have their own benefits and drawbacks, but I'll let their own developers speak to those, especially as I haven't tested any, yet. Here are a few; let me know if you're aware of others:
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)!