From 6acd32748ed12f744c95698622ecd23b2a126489 Mon Sep 17 00:00:00 2001 From: Daniel Hunsaker Date: Sat, 12 Oct 2013 00:37:02 -0600 Subject: [PATCH] Inital Release Version Version 0.0.0 - Completed demo - Fixed bugs highlighted by the demo - Added disclaimer about the ugliness of the demo - Added info on where to find the project in the README.md Signed-off-by: Daniel Hunsaker --- README.md | 19 ++++- demo/app.js | 72 ++++++++++++++++-- demo/form-template.js | 171 +++++++++++++++++++++++++++++++++++++++--- demo/index.html | 15 +++- dynamic-forms.js | 109 ++++++++++++++++++++------- 5 files changed, 336 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index f2b6ff7..3357d4a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ Build Forms in AngularJS From Nothing But JSON Installation ------------ -Copy `dynamic-forms.js` into your project wherever your other assets reside. +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. Use --- @@ -51,8 +52,8 @@ As with any other [AngularJS][] module: And that's about it! -Full Specification ------------------- +The TL;DR Version +----------------- ### The Directive ### You invoke the `dynamic-form` directive using an element (``) - other options (such as class, attribute, and comment) are unsupported (for now). The directive requires @@ -165,6 +166,8 @@ what][formsupport]) * The key of each child object specifies the key to associate with the checkbox it describes * `class`: applies a specific [`ng-class`][] to the current checkbox, independently of the rest + * `label`: operates identically to the standard `label` option, but applies to a specific + checkbox in the list * See the [checkbox](#checkbox) type for other fields supported here * __Other Notes:__ * This is a convenience type, used to tie a group of [checkbox](#checkbox) controls together @@ -425,6 +428,14 @@ Acknowledgements * [K. Scott Allen][] for the [file input directive][filedirective] and the [FileReader service][fileservice] adapted for use here. +Issues And Assistance +--------------------- +If you notice a problem, let me know about it on [GitHub][issues-github] or +[Bitbucket][issues-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)! + [colorsupport]: http://caniuse.com/input-color [datesupport]: http://caniuse.com/input-datetime [filedirective]: http://odetocode.com/blogs/scott/archive/2013/07/05/a-file-input-directive-for-angularjs.aspx @@ -434,6 +445,8 @@ Acknowledgements [pholdsupport]: http://caniuse.com/input-placeholder [rangesupport]: http://caniuse.com/input-range +[GitHub]: https://github.com/danhunsaker/angular-dynamic-forms +[BitBucket]: https://bitbucket.org/danhunsaker/angular-dynamic-forms [issues-github]: https://github.com/danhunsaker/angular-dynamic-forms/issues [issues-bitbucket]: https://bitbucket.org/danhunsaker/angular-dynamic-forms/issues diff --git a/demo/app.js b/demo/app.js index c4d6d85..36c4a34 100644 --- a/demo/app.js +++ b/demo/app.js @@ -54,7 +54,9 @@ angular.module('app', ['dynform']) "textarea": { "type": "textarea", "label": "textarea", - "placeholder": "textarea" + "placeholder": "textarea", + "splitBy": "\n", + "val": ["This array should be","separated by new lines"] }, "time": { "type": "time", @@ -94,15 +96,71 @@ angular.module('app', ['dynform']) }, "select": { "type": "select", - "label": "select" + "label": "select", + "empty": "nothing selected", + "options": { + "first": { + "label": "first option" + }, + "second": { + "label": "second option", + "group": "first group" + }, + "third": { + "label": "third option", + "group": "second group" + }, + "fourth": { + "label": "fourth option", + "group": "first group" + }, + "fifth": { + "label": "fifth option" + }, + "sixth": { + "label": "sixth option", + "group": "second group" + }, + "seventh": { + "label": "seventh option" + }, + "eighth": { + "label": "eighth option", + "group": "first group" + }, + "ninth": { + "label": "ninth option", + "group": "second group" + }, + "tenth": { + "label": "tenth option" + } + } }, "checklist": { "type": "checklist", - "label": "checklist" + "label": "checklist", + "options": { + "first": { + "label": "first option" + }, + "second": { + "label": "second option", + "isOn": "on", + "isOff": "off" + } + } }, "radio": { "type": "radio", - "label": "radio" + "label": "radio", + "values": { + "first": "first option", + "second": "second option", + "third": "third option", + "fourth": "fourth option", + "fifth": "fifth option" + } }, "button": { "type": "button", @@ -110,11 +168,13 @@ angular.module('app', ['dynform']) }, "hidden": { "type": "hidden", - "label": "hidden" + "label": "hidden", + "val": "hidden" }, "image": { "type": "image", - "label": "image" + "label": "image", + "source": "http://angularjs.org/img/AngularJS-large.png" }, "legend": { "type": "legend", diff --git a/demo/form-template.js b/demo/form-template.js index 0d7553a..2b05629 100644 --- a/demo/form-template.js +++ b/demo/form-template.js @@ -1,42 +1,193 @@ { + "bogus": { + "type": "bogus", + "label": "bogus" + }, + "button": { + "type": "button", + "label": "button" + }, + "checkbox": { + "type": "checkbox", + "label": "checkbox" + }, + "checklist": { + "type": "checklist", + "label": "checklist", + "options": { + "first": { + "label": "first option" + }, + "second": { + "label": "second option", + "isOn": "on", + "isOff": "off" + } + } + }, + "color": { + "type": "color", + "label": "color" + }, "date": { "type": "date", - "label": "date" + "label": "date", + "placeholder": "date" }, "datetime": { "type": "datetime", - "label": "datetime" + "label": "datetime", + "placeholder": "datetime" }, "datetime-local": { "type": "datetime-local", - "label": "datetime-local" + "label": "datetime-local", + "placeholder": "datetime-local" }, "email": { "type": "email", - "label": "email" + "label": "email", + "placeholder": "email" + }, + "file": { + "type": "file", + "label": "file", + "multiple": true + }, + "hidden": { + "type": "hidden", + "label": "hidden", + "val": "hidden" + }, + "image": { + "type": "image", + "label": "image", + "source": "http://angularjs.org/img/AngularJS-large.png" + }, + "legend": { + "type": "legend", + "label": "legend" + }, + "month": { + "type": "month", + "label": "month", + "placeholder": "month" }, "number": { "type": "number", - "label": "number" + "label": "number", + "placeholder": "number" }, "password": { "type": "password", - "label": "password" + "label": "password", + "placeholder": "password" + }, + "radio": { + "type": "radio", + "label": "radio", + "values": { + "first": "first option", + "second": "second option", + "third": "third option", + "fourth": "fourth option", + "fifth": "fifth option" + } + }, + "range": { + "type": "range", + "label": "range", + "model": "number", + "val": 42, + "minValue": -42, + "maxValue": 84 + }, + "reset": { + "type": "reset", + "label": "reset" + }, + "search": { + "type": "search", + "label": "search", + "placeholder": "search" + }, + "select": { + "type": "select", + "label": "select", + "empty": "nothing selected", + "options": { + "first": { + "label": "first option" + }, + "second": { + "label": "second option", + "group": "first group" + }, + "third": { + "label": "third option", + "group": "second group" + }, + "fourth": { + "label": "fourth option", + "group": "first group" + }, + "fifth": { + "label": "fifth option" + }, + "sixth": { + "label": "sixth option", + "group": "second group" + }, + "seventh": { + "label": "seventh option" + }, + "eighth": { + "label": "eighth option", + "group": "first group" + }, + "ninth": { + "label": "ninth option", + "group": "second group" + }, + "tenth": { + "label": "tenth option" + } + } + }, + "submit": { + "type": "submit", + "label": "submit" + }, + "tel": { + "type": "tel", + "label": "tel", + "placeholder": "tel" }, "text": { "type": "text", - "label": "text" + "label": "text", + "placeholder": "text" }, "textarea": { "type": "textarea", - "label": "textarea" + "label": "textarea", + "placeholder": "textarea", + "splitBy": "\n", + "val": ["This array should be","separated by new lines"] }, "time": { "type": "time", - "label": "time" + "label": "time", + "placeholder": "time" }, "url": { "type": "url", - "label": "url" + "label": "url", + "placeholder": "url" + }, + "week": { + "type": "week", + "label": "week", + "placeholder": "week" } } \ No newline at end of file diff --git a/demo/index.html b/demo/index.html index 0ef800c..4e94468 100644 --- a/demo/index.html +++ b/demo/index.html @@ -2,7 +2,7 @@ Angular Dynamic Forms Test - + - + +

This demo is a really ugly example of the basic functionality of this module. Have a look + through the code and get a feel for how everything works.

template= test diff --git a/dynamic-forms.js b/dynamic-forms.js index e3d29c7..45943d5 100644 --- a/dynamic-forms.js +++ b/dynamic-forms.js @@ -134,6 +134,7 @@ angular.module('dynform', []) else if (field.type === 'checklist') { if (angular.isDefined(field.val)) {model[field.model] = angular.copy(field.val);} if (angular.isDefined(field.options)) { + model[field.model] = {}; angular.forEach(field.options, function (option, childId) { newChild = angular.element(''); newChild.attr('name', field.model + '.' + childId); @@ -209,7 +210,9 @@ angular.module('dynform', []) }); if (!angular.equals(optGroups, {})) { - newElement.append(optGroups); + angular.forEach(optGroups, function (optGroup) { + newElement.append(optGroup); + }); optGroups = {}; } } @@ -289,7 +292,6 @@ angular.module('dynform', []) } // Psuedo-transclusion - newElement.addClass('dynamic-form'); angular.forEach(attrs.$attr, function(attName, attIndex) { newElement.attr(attName, attrs[attIndex]); }); @@ -298,6 +300,7 @@ angular.module('dynform', []) angular.forEach(element[0].classList, function(clsName) { newElement[0].classList.add(clsName); }); + newElement.addClass('dynamic-form'); newElement.append(element.contents()); // onReset logic @@ -324,37 +327,85 @@ angular.module('dynform', []) } }; }]) - // Following code was adapted from http://odetocode.com/blogs/scott/archive/2013/07/05/a-file-input-directive-for-angularjs.aspx - .directive('input', ['$parse', function ($parse) { - return { - restrict: 'E', - require: '?ngModel', - link: function (scope, element, attrs, ctrl) { - if (attrs.type === 'file') { - var modelGet = $parse(attrs.ngModel), - modelSet = modelGet.assign, - onChange = $parse(attrs.onChange), - updateModel = function () { - scope.$apply(function () { - modelSet(scope, element[0].files); - onChange(scope); - }); - }; - - ctrl.$render = function () { - element[0].files = this.$viewValue; - }; - element.bind('change', updateModel); + // Not a fan of how Angular's ngList is implemented, so here's a better one (IMO). It will ONLY + // apply to child elements, and replaces the ngList that ships with Angular. + .directive('ngList', [function () { + return { + require: '?ngModel', + link: function (scope, element, attr, ctrl) { + var match = /\/(.*)\//.exec(element.attr(attr.$attr.ngList)), + separator = match && new RegExp(match[1]) || element.attr(attr.$attr.ngList) || ','; + + if (element[0].form !== null && !angular.element(element[0].form).hasClass('dynamic-form')) { + return; + } + + ctrl.$parsers.splice(0, 1); + ctrl.$formatters.splice(0, 1); + + ctrl.$parsers.push(function(viewValue) { + var list = []; + + if (angular.isString(viewValue)) { + // Don't have Angular's trim() exposed, so let's simulate it: + if (String.prototype.trim) { + angular.forEach(viewValue.split(separator), function(value) { + if (value) list.push(value.trim()); + }); } - else if (attrs.type === 'range') { - ctrl.$parsers.push(function (val) { - if (val) { - return parseFloat(val); - } + else { + angular.forEach(viewValue.split(separator), function(value) { + if (value) list.push(value.replace(/^\s*/, '').replace(/\s*$/, '')); }); } } - }; + + return list; + }); + + ctrl.$formatters.push(function(val) { + var joinBy = angular.isString(separator) && separator || ', '; + + if (angular.isArray(val)) { + return val.join(joinBy); + } + + return undefined; + }); + } + }; + }]) + // Following code was adapted from http://odetocode.com/blogs/scott/archive/2013/07/05/a-file-input-directive-for-angularjs.aspx + .directive('input', ['$parse', function ($parse) { + return { + restrict: 'E', + require: '?ngModel', + link: function (scope, element, attrs, ctrl) { + if (attrs.type === 'file') { + var modelGet = $parse(attrs.ngModel), + modelSet = modelGet.assign, + onChange = $parse(attrs.onChange), + updateModel = function () { + scope.$apply(function () { + modelSet(scope, element[0].files); + onChange(scope); + }); + }; + + ctrl.$render = function () { + element[0].files = this.$viewValue; + }; + element.bind('change', updateModel); + } + else if (attrs.type === 'range') { + ctrl.$parsers.push(function (val) { + if (val) { + return parseFloat(val); + } + }); + } + } + }; }]) // Following code was adapted from http://odetocode.com/blogs/scott/archive/2013/07/03/building-a-filereader-service-for-angularjs-the-service.aspx .factory('fileReader', ['$q', function ($q) {