danhunsaker / angular-dynamic-forms

Build Forms in AngularJS From Nothing But JSON (please see Alternatives in the README)
MIT License
379 stars 140 forks source link

angular-dynamic-forms

Join the chat at https://gitter.im/danhunsaker/angular-dynamic-forms Build Forms in AngularJS From Nothing But JSON

Uses the MIT License. See LICENSE file for details.

Installation

Bower

The easy way.

  1. bower install angular-dynforms (add --save if you want to add the dependency to your own project - HIGHLY RECOMMENDED)

Git

The old way.

  1. Clone the project from either GitHub or BitBucket - whichever you prefer.
  2. Copy dynamic-forms.js into your project wherever your other assets reside.

Name Change

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.

Use

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');
    <!--[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]-->
    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.

The TL;DR Version

The Directive

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-forms 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.

The Template

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.

Supported Control Types

Following is a list of all currently-supported types, 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 types are properly supported in all browsers, yet; there are a number of references around the web for which browsers support what.

Common Options

button

checkbox

checklist

color

date

datetime

datetime-local

email

fieldset

file

hidden

image

legend

month

number

password

radio

range

reset

search

select

submit

tel

text

textarea

time

url

week

Acknowledgements

Alternatives

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:

Issues And Assistance

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)!