From cd45f647d2ab4b91bde7fabe32814c9bb3849558 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Wed, 19 Aug 2015 10:28:46 +0200 Subject: [PATCH 01/23] Removed decorator --- dist/bootstrap-decorator.js | 90 ------------------- dist/bootstrap-decorator.min.js | 1 - .../decorators/bootstrap/actions-trcl.html | 1 - .../decorators/bootstrap/actions.html | 13 --- .../decorators/bootstrap/array.html | 28 ------ .../bootstrap/bootstrap-decorator.js | 60 ------------- .../decorators/bootstrap/checkbox.html | 15 ---- .../decorators/bootstrap/checkboxes.html | 18 ---- .../decorators/bootstrap/default.html | 54 ----------- .../decorators/bootstrap/fieldset-trcl.html | 5 -- .../decorators/bootstrap/fieldset.html | 5 -- src/directives/decorators/bootstrap/help.html | 1 - .../decorators/bootstrap/radio-buttons.html | 24 ----- .../decorators/bootstrap/radios-inline.html | 18 ---- .../decorators/bootstrap/radios.html | 18 ---- .../decorators/bootstrap/section.html | 3 - .../decorators/bootstrap/select.html | 16 ---- .../decorators/bootstrap/submit.html | 15 ---- .../decorators/bootstrap/tabarray.html | 58 ------------ src/directives/decorators/bootstrap/tabs.html | 21 ----- .../decorators/bootstrap/textarea.html | 35 -------- 21 files changed, 499 deletions(-) delete mode 100644 dist/bootstrap-decorator.js delete mode 100644 dist/bootstrap-decorator.min.js delete mode 100644 src/directives/decorators/bootstrap/actions-trcl.html delete mode 100644 src/directives/decorators/bootstrap/actions.html delete mode 100644 src/directives/decorators/bootstrap/array.html delete mode 100644 src/directives/decorators/bootstrap/bootstrap-decorator.js delete mode 100644 src/directives/decorators/bootstrap/checkbox.html delete mode 100644 src/directives/decorators/bootstrap/checkboxes.html delete mode 100644 src/directives/decorators/bootstrap/default.html delete mode 100644 src/directives/decorators/bootstrap/fieldset-trcl.html delete mode 100644 src/directives/decorators/bootstrap/fieldset.html delete mode 100644 src/directives/decorators/bootstrap/help.html delete mode 100644 src/directives/decorators/bootstrap/radio-buttons.html delete mode 100644 src/directives/decorators/bootstrap/radios-inline.html delete mode 100644 src/directives/decorators/bootstrap/radios.html delete mode 100644 src/directives/decorators/bootstrap/section.html delete mode 100644 src/directives/decorators/bootstrap/select.html delete mode 100644 src/directives/decorators/bootstrap/submit.html delete mode 100644 src/directives/decorators/bootstrap/tabarray.html delete mode 100644 src/directives/decorators/bootstrap/tabs.html delete mode 100644 src/directives/decorators/bootstrap/textarea.html diff --git a/dist/bootstrap-decorator.js b/dist/bootstrap-decorator.js deleted file mode 100644 index cfd86cb0f..000000000 --- a/dist/bootstrap-decorator.js +++ /dev/null @@ -1,90 +0,0 @@ -(function(root, factory) { - if (typeof define === 'function' && define.amd) { - define(['schemaForm'], factory); - } else if (typeof exports === 'object') { - module.exports = factory(require('schemaForm')); - } else { - root.bootstrapDecorator = factory(root.schemaForm); - } -}(this, function(schemaForm) { -angular.module("schemaForm").run(["$templateCache", function($templateCache) {$templateCache.put("directives/decorators/bootstrap/actions-trcl.html","
"); -$templateCache.put("directives/decorators/bootstrap/actions.html","
"); -$templateCache.put("directives/decorators/bootstrap/array.html","
"); -$templateCache.put("directives/decorators/bootstrap/checkbox.html","
"); -$templateCache.put("directives/decorators/bootstrap/checkboxes.html","
"); -$templateCache.put("directives/decorators/bootstrap/default.html","
{{ hasSuccess() ? \'(success)\' : \'(error)\' }}
"); -$templateCache.put("directives/decorators/bootstrap/fieldset-trcl.html","
{{ form.title }}
"); -$templateCache.put("directives/decorators/bootstrap/fieldset.html","
{{ form.title }}
"); -$templateCache.put("directives/decorators/bootstrap/help.html","
"); -$templateCache.put("directives/decorators/bootstrap/radio-buttons.html","
"); -$templateCache.put("directives/decorators/bootstrap/radios-inline.html","
"); -$templateCache.put("directives/decorators/bootstrap/radios.html","
"); -$templateCache.put("directives/decorators/bootstrap/section.html","
"); -$templateCache.put("directives/decorators/bootstrap/select.html","
"); -$templateCache.put("directives/decorators/bootstrap/submit.html","
"); -$templateCache.put("directives/decorators/bootstrap/tabarray.html","
"); -$templateCache.put("directives/decorators/bootstrap/tabs.html","
"); -$templateCache.put("directives/decorators/bootstrap/textarea.html","
");}]); -angular.module('schemaForm').config(['schemaFormDecoratorsProvider', function(decoratorsProvider) { - var base = 'directives/decorators/bootstrap/'; - - decoratorsProvider.defineDecorator('bootstrapDecorator', { - textarea: {template: base + 'textarea.html', replace: false}, - fieldset: {template: base + 'fieldset.html', replace: false}, - /*fieldset: {template: base + 'fieldset.html', replace: true, builder: function(args) { - var children = args.build(args.form.items, args.path + '.items'); - console.log('fieldset children frag', children.childNodes) - args.fieldFrag.childNode.appendChild(children); - }},*/ - array: {template: base + 'array.html', replace: false}, - tabarray: {template: base + 'tabarray.html', replace: false}, - tabs: {template: base + 'tabs.html', replace: false}, - section: {template: base + 'section.html', replace: false}, - conditional: {template: base + 'section.html', replace: false}, - actions: {template: base + 'actions.html', replace: false}, - select: {template: base + 'select.html', replace: false}, - checkbox: {template: base + 'checkbox.html', replace: false}, - checkboxes: {template: base + 'checkboxes.html', replace: false}, - number: {template: base + 'default.html', replace: false}, - password: {template: base + 'default.html', replace: false}, - submit: {template: base + 'submit.html', replace: false}, - button: {template: base + 'submit.html', replace: false}, - radios: {template: base + 'radios.html', replace: false}, - 'radios-inline': {template: base + 'radios-inline.html', replace: false}, - radiobuttons: {template: base + 'radio-buttons.html', replace: false}, - help: {template: base + 'help.html', replace: false}, - 'default': {template: base + 'default.html', replace: false} - }, []); - - //manual use directives - decoratorsProvider.createDirectives({ - textarea: base + 'textarea.html', - select: base + 'select.html', - checkbox: base + 'checkbox.html', - checkboxes: base + 'checkboxes.html', - number: base + 'default.html', - submit: base + 'submit.html', - button: base + 'submit.html', - text: base + 'default.html', - date: base + 'default.html', - password: base + 'default.html', - datepicker: base + 'datepicker.html', - input: base + 'default.html', - radios: base + 'radios.html', - 'radios-inline': base + 'radios-inline.html', - radiobuttons: base + 'radio-buttons.html', - }); - -}]).directive('sfFieldset', function() { - return { - transclude: true, - scope: true, - templateUrl: 'directives/decorators/bootstrap/fieldset-trcl.html', - link: function(scope, element, attrs) { - scope.title = scope.$eval(attrs.title); - } - }; -}); - -return schemaForm; -})); diff --git a/dist/bootstrap-decorator.min.js b/dist/bootstrap-decorator.min.js deleted file mode 100644 index 476ba5ae3..000000000 --- a/dist/bootstrap-decorator.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(e,t){"function"==typeof define&&define.amd?define(["schemaForm"],t):"object"==typeof exports?module.exports=t(require("schemaForm")):e.bootstrapDecorator=t(e.schemaForm)}(this,function(e){return angular.module("schemaForm").run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/actions-trcl.html",'
'),e.put("directives/decorators/bootstrap/actions.html",'
'),e.put("directives/decorators/bootstrap/array.html",'
'),e.put("directives/decorators/bootstrap/checkbox.html",'
'),e.put("directives/decorators/bootstrap/checkboxes.html",'
'),e.put("directives/decorators/bootstrap/default.html",'
{{ hasSuccess() ? \'(success)\' : \'(error)\' }}
'),e.put("directives/decorators/bootstrap/fieldset-trcl.html",'
{{ form.title }}
'),e.put("directives/decorators/bootstrap/fieldset.html",'
{{ form.title }}
'),e.put("directives/decorators/bootstrap/help.html",'
'),e.put("directives/decorators/bootstrap/radio-buttons.html",'
'),e.put("directives/decorators/bootstrap/radios-inline.html",'
'),e.put("directives/decorators/bootstrap/radios.html",'
'),e.put("directives/decorators/bootstrap/section.html",'
'),e.put("directives/decorators/bootstrap/select.html",'
'),e.put("directives/decorators/bootstrap/submit.html",'
'),e.put("directives/decorators/bootstrap/tabarray.html",'
'),e.put("directives/decorators/bootstrap/tabs.html",'
'),e.put("directives/decorators/bootstrap/textarea.html",'
')}]),angular.module("schemaForm").config(["schemaFormDecoratorsProvider",function(e){var t="directives/decorators/bootstrap/";e.defineDecorator("bootstrapDecorator",{textarea:{template:t+"textarea.html",replace:!1},fieldset:{template:t+"fieldset.html",replace:!1},array:{template:t+"array.html",replace:!1},tabarray:{template:t+"tabarray.html",replace:!1},tabs:{template:t+"tabs.html",replace:!1},section:{template:t+"section.html",replace:!1},conditional:{template:t+"section.html",replace:!1},actions:{template:t+"actions.html",replace:!1},select:{template:t+"select.html",replace:!1},checkbox:{template:t+"checkbox.html",replace:!1},checkboxes:{template:t+"checkboxes.html",replace:!1},number:{template:t+"default.html",replace:!1},password:{template:t+"default.html",replace:!1},submit:{template:t+"submit.html",replace:!1},button:{template:t+"submit.html",replace:!1},radios:{template:t+"radios.html",replace:!1},"radios-inline":{template:t+"radios-inline.html",replace:!1},radiobuttons:{template:t+"radio-buttons.html",replace:!1},help:{template:t+"help.html",replace:!1},"default":{template:t+"default.html",replace:!1}},[]),e.createDirectives({textarea:t+"textarea.html",select:t+"select.html",checkbox:t+"checkbox.html",checkboxes:t+"checkboxes.html",number:t+"default.html",submit:t+"submit.html",button:t+"submit.html",text:t+"default.html",date:t+"default.html",password:t+"default.html",datepicker:t+"datepicker.html",input:t+"default.html",radios:t+"radios.html","radios-inline":t+"radios-inline.html",radiobuttons:t+"radio-buttons.html"})}]).directive("sfFieldset",function(){return{transclude:!0,scope:!0,templateUrl:"directives/decorators/bootstrap/fieldset-trcl.html",link:function(e,t,s){e.title=e.$eval(s.title)}}}),e}); \ No newline at end of file diff --git a/src/directives/decorators/bootstrap/actions-trcl.html b/src/directives/decorators/bootstrap/actions-trcl.html deleted file mode 100644 index 0b32cbc2f..000000000 --- a/src/directives/decorators/bootstrap/actions-trcl.html +++ /dev/null @@ -1 +0,0 @@ -
diff --git a/src/directives/decorators/bootstrap/actions.html b/src/directives/decorators/bootstrap/actions.html deleted file mode 100644 index ddf0789e1..000000000 --- a/src/directives/decorators/bootstrap/actions.html +++ /dev/null @@ -1,13 +0,0 @@ -
- - -
diff --git a/src/directives/decorators/bootstrap/array.html b/src/directives/decorators/bootstrap/array.html deleted file mode 100644 index f46838639..000000000 --- a/src/directives/decorators/bootstrap/array.html +++ /dev/null @@ -1,28 +0,0 @@ -
- -
    -
  1. - - -
  2. -
-
- -
-
-
diff --git a/src/directives/decorators/bootstrap/bootstrap-decorator.js b/src/directives/decorators/bootstrap/bootstrap-decorator.js deleted file mode 100644 index 4f0d5bb7e..000000000 --- a/src/directives/decorators/bootstrap/bootstrap-decorator.js +++ /dev/null @@ -1,60 +0,0 @@ -angular.module('schemaForm').config(['schemaFormDecoratorsProvider', function(decoratorsProvider) { - var base = 'directives/decorators/bootstrap/'; - - decoratorsProvider.defineDecorator('bootstrapDecorator', { - textarea: {template: base + 'textarea.html', replace: false}, - fieldset: {template: base + 'fieldset.html', replace: false}, - /*fieldset: {template: base + 'fieldset.html', replace: true, builder: function(args) { - var children = args.build(args.form.items, args.path + '.items'); - console.log('fieldset children frag', children.childNodes) - args.fieldFrag.childNode.appendChild(children); - }},*/ - array: {template: base + 'array.html', replace: false}, - tabarray: {template: base + 'tabarray.html', replace: false}, - tabs: {template: base + 'tabs.html', replace: false}, - section: {template: base + 'section.html', replace: false}, - conditional: {template: base + 'section.html', replace: false}, - actions: {template: base + 'actions.html', replace: false}, - select: {template: base + 'select.html', replace: false}, - checkbox: {template: base + 'checkbox.html', replace: false}, - checkboxes: {template: base + 'checkboxes.html', replace: false}, - number: {template: base + 'default.html', replace: false}, - password: {template: base + 'default.html', replace: false}, - submit: {template: base + 'submit.html', replace: false}, - button: {template: base + 'submit.html', replace: false}, - radios: {template: base + 'radios.html', replace: false}, - 'radios-inline': {template: base + 'radios-inline.html', replace: false}, - radiobuttons: {template: base + 'radio-buttons.html', replace: false}, - help: {template: base + 'help.html', replace: false}, - 'default': {template: base + 'default.html', replace: false} - }, []); - - //manual use directives - decoratorsProvider.createDirectives({ - textarea: base + 'textarea.html', - select: base + 'select.html', - checkbox: base + 'checkbox.html', - checkboxes: base + 'checkboxes.html', - number: base + 'default.html', - submit: base + 'submit.html', - button: base + 'submit.html', - text: base + 'default.html', - date: base + 'default.html', - password: base + 'default.html', - datepicker: base + 'datepicker.html', - input: base + 'default.html', - radios: base + 'radios.html', - 'radios-inline': base + 'radios-inline.html', - radiobuttons: base + 'radio-buttons.html', - }); - -}]).directive('sfFieldset', function() { - return { - transclude: true, - scope: true, - templateUrl: 'directives/decorators/bootstrap/fieldset-trcl.html', - link: function(scope, element, attrs) { - scope.title = scope.$eval(attrs.title); - } - }; -}); diff --git a/src/directives/decorators/bootstrap/checkbox.html b/src/directives/decorators/bootstrap/checkbox.html deleted file mode 100644 index d6ad64d4b..000000000 --- a/src/directives/decorators/bootstrap/checkbox.html +++ /dev/null @@ -1,15 +0,0 @@ -
- -
-
diff --git a/src/directives/decorators/bootstrap/checkboxes.html b/src/directives/decorators/bootstrap/checkboxes.html deleted file mode 100644 index 45b514135..000000000 --- a/src/directives/decorators/bootstrap/checkboxes.html +++ /dev/null @@ -1,18 +0,0 @@ -
- -
- - -
-
-
diff --git a/src/directives/decorators/bootstrap/default.html b/src/directives/decorators/bootstrap/default.html deleted file mode 100644 index b5685042b..000000000 --- a/src/directives/decorators/bootstrap/default.html +++ /dev/null @@ -1,54 +0,0 @@ -
- - - - -
- - - - -
- - - - {{ hasSuccess() ? '(success)' : '(error)' }} - -
-
diff --git a/src/directives/decorators/bootstrap/fieldset-trcl.html b/src/directives/decorators/bootstrap/fieldset-trcl.html deleted file mode 100644 index e4069bd77..000000000 --- a/src/directives/decorators/bootstrap/fieldset-trcl.html +++ /dev/null @@ -1,5 +0,0 @@ -
- {{ form.title }} -
-
-
diff --git a/src/directives/decorators/bootstrap/fieldset.html b/src/directives/decorators/bootstrap/fieldset.html deleted file mode 100644 index 4db3f059b..000000000 --- a/src/directives/decorators/bootstrap/fieldset.html +++ /dev/null @@ -1,5 +0,0 @@ -
- {{ form.title }} -
- -
diff --git a/src/directives/decorators/bootstrap/help.html b/src/directives/decorators/bootstrap/help.html deleted file mode 100644 index aca5bb56a..000000000 --- a/src/directives/decorators/bootstrap/help.html +++ /dev/null @@ -1 +0,0 @@ -
diff --git a/src/directives/decorators/bootstrap/radio-buttons.html b/src/directives/decorators/bootstrap/radio-buttons.html deleted file mode 100644 index 5a12dc86a..000000000 --- a/src/directives/decorators/bootstrap/radio-buttons.html +++ /dev/null @@ -1,24 +0,0 @@ -
-
- -
-
- -
-
-
diff --git a/src/directives/decorators/bootstrap/radios-inline.html b/src/directives/decorators/bootstrap/radios-inline.html deleted file mode 100644 index 6c3d07928..000000000 --- a/src/directives/decorators/bootstrap/radios-inline.html +++ /dev/null @@ -1,18 +0,0 @@ -
- -
- -
-
-
diff --git a/src/directives/decorators/bootstrap/radios.html b/src/directives/decorators/bootstrap/radios.html deleted file mode 100644 index f3b73189b..000000000 --- a/src/directives/decorators/bootstrap/radios.html +++ /dev/null @@ -1,18 +0,0 @@ -
- -
- -
-
-
diff --git a/src/directives/decorators/bootstrap/section.html b/src/directives/decorators/bootstrap/section.html deleted file mode 100644 index 57224a290..000000000 --- a/src/directives/decorators/bootstrap/section.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/src/directives/decorators/bootstrap/select.html b/src/directives/decorators/bootstrap/select.html deleted file mode 100644 index b4b3296e0..000000000 --- a/src/directives/decorators/bootstrap/select.html +++ /dev/null @@ -1,16 +0,0 @@ -
- - -
-
diff --git a/src/directives/decorators/bootstrap/submit.html b/src/directives/decorators/bootstrap/submit.html deleted file mode 100644 index f2b666195..000000000 --- a/src/directives/decorators/bootstrap/submit.html +++ /dev/null @@ -1,15 +0,0 @@ -
- - -
diff --git a/src/directives/decorators/bootstrap/tabarray.html b/src/directives/decorators/bootstrap/tabarray.html deleted file mode 100644 index a6ee56f2a..000000000 --- a/src/directives/decorators/bootstrap/tabarray.html +++ /dev/null @@ -1,58 +0,0 @@ - -
-
- -
- -
-
-
- - - - -
-
-
- - - -
diff --git a/src/directives/decorators/bootstrap/tabs.html b/src/directives/decorators/bootstrap/tabs.html deleted file mode 100644 index 747a358bd..000000000 --- a/src/directives/decorators/bootstrap/tabs.html +++ /dev/null @@ -1,21 +0,0 @@ -
- - -
-
- - -
-
-
diff --git a/src/directives/decorators/bootstrap/textarea.html b/src/directives/decorators/bootstrap/textarea.html deleted file mode 100644 index 06364edfc..000000000 --- a/src/directives/decorators/bootstrap/textarea.html +++ /dev/null @@ -1,35 +0,0 @@ -
- - - - -
- - - -
- - -
From 56c1fed6b79fc070f986d6e67cb3405cacbd7019 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Wed, 19 Aug 2015 10:38:21 +0200 Subject: [PATCH 02/23] removed examples and gulp tasks --- examples/bootstrap-example.html | 389 ------------------------ examples/custom-validators.html | 123 -------- examples/data/array.json | 72 ----- examples/data/complex-keys.json | 62 ---- examples/data/conditional-required.json | 39 --- examples/data/grid.json | 57 ---- examples/data/simple.json | 39 --- examples/data/sink.json | 266 ---------------- examples/data/tabarray.json | 62 ---- examples/data/titlemaps.json | 97 ------ examples/data/types.json | 26 -- gulp/tasks/bootstrap-datepicker.js | 29 -- gulp/tasks/bootstrap.js | 42 --- gulp/tasks/protractor.js | 38 --- 14 files changed, 1341 deletions(-) delete mode 100644 examples/bootstrap-example.html delete mode 100644 examples/custom-validators.html delete mode 100644 examples/data/array.json delete mode 100644 examples/data/complex-keys.json delete mode 100644 examples/data/conditional-required.json delete mode 100644 examples/data/grid.json delete mode 100644 examples/data/simple.json delete mode 100644 examples/data/sink.json delete mode 100644 examples/data/tabarray.json delete mode 100644 examples/data/titlemaps.json delete mode 100644 examples/data/types.json delete mode 100644 gulp/tasks/bootstrap-datepicker.js delete mode 100644 gulp/tasks/bootstrap.js delete mode 100644 gulp/tasks/protractor.js diff --git a/examples/bootstrap-example.html b/examples/bootstrap-example.html deleted file mode 100644 index 89e57fdf3..000000000 --- a/examples/bootstrap-example.html +++ /dev/null @@ -1,389 +0,0 @@ - - - - - Bootstrap Schema Form example - - - - - - - - - - - - -
-

Schema Form Example

-
-
-

The Generated Form

- -
- -
Form is valid
-
Form is not valid
- -

Model

-
{{pretty()}}
-
-
-

Select Example

-
- - - By the way, there is also an example of - custom (async) validators example. - -
-

Form

-
-

Schema

-
-
-
-
-

- - JavaScript license information - -

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/custom-validators.html b/examples/custom-validators.html deleted file mode 100644 index eb6cd73ad..000000000 --- a/examples/custom-validators.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - Custom validators, async validators etc - - - - - -

Demo of custom validators, async validators and parsers

- Check the source -
-
-
- The form is pristinedirty - and validinvalid. -
-
{{prettyModel}}
-
- - - - - - - - - - - - - - - diff --git a/examples/data/array.json b/examples/data/array.json deleted file mode 100644 index e19eb90f9..000000000 --- a/examples/data/array.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "schema": { - "type": "object", - "title": "Comment", - "required": ["comments"], - "properties": { - "comments": { - "type": "array", - "maxItems": 2, - "items": { - "type": "object", - "properties": { - "name": { - "title": "Name", - "type": "string" - }, - "email": { - "title": "Email", - "type": "string", - "pattern": "^\\S+@\\S+$", - "description": "Email will be used for evil." - }, - "spam": { - "title": "Spam", - "type": "boolean", - "default": true - }, - "comment": { - "title": "Comment", - "type": "string", - "maxLength": 20, - "validationMessage": "Don't be greedy!" - } - }, - "required": ["name","comment"] - } - } - } - }, - "form": [ - { - "type": "help", - "helpvalue": "

Array Example

Try adding a couple of forms, reorder by drag'n'drop.

" - }, - { - "key": "comments", - "add": "New", - "style": { - "add": "btn-success" - }, - "items": [ - "comments[].name", - "comments[].email", - { - "key": "comments[].spam", - "type": "checkbox", - "title": "Yes I want spam.", - "condition": "model.comments[arrayIndex].email" - }, - { - "key": "comments[].comment", - "type": "textarea" - } - ] - }, - { - "type": "submit", - "style": "btn-info", - "title": "OK" - } - ] -} diff --git a/examples/data/complex-keys.json b/examples/data/complex-keys.json deleted file mode 100644 index cccac7749..000000000 --- a/examples/data/complex-keys.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "schema": { - "type": "object", - "title": "Complex Key Support", - "properties": { - "a[\"b\"].c": { - "type": "string" - }, - "simple": { - "type": "object", - "properties": { - "prøp": { - "title": "UTF8 in both dot and bracket notation", - "type": "string" - } - } - }, - "array-key": { - "type": "array", - "items": { - "type": "object", - "properties": { - "a'rr[\"l": { - "title": "Control Characters", - "type": "string" - }, - "˙∆∂∞˚¬": { - "type": "string" - } - }, - "required": [ - "a'rr[\"l", - "˙∆∂∞˚¬" - ] - } - } - } - }, - "form": [ - { - "type": "help", - "helpvalue": "Complex keys are only supported with AngularJS version 1.3.x, see known limitations in the docs." - }, - "['a[\"b\"].c']", - { - "key": "array-key", - "items": [ - "['array-key'][]['a'rr[\"l']", - { - "key": "['array-key'][]['˙∆∂∞˚¬']", - "title": "Unicode Characters" - } - ] - }, - { - "key": "simple", - "items": [ - "simple.prøp" - ] - } - ] -} diff --git a/examples/data/conditional-required.json b/examples/data/conditional-required.json deleted file mode 100644 index 251119dec..000000000 --- a/examples/data/conditional-required.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "schema": { - "type": "object", - "properties": { - "switch": { - "title": "Spam me, please", - "type": "boolean" - }, - "email": { - "title": "Email", - "type": "string", - "pattern": "^\\S+@\\S+$", - "description": "Email will be used for evil." - } - }, - "required": ["switch"] - }, - "form": [ - { - "type": "help", - "helpvalue": "

Schema Form does not support oneOf (yet), but you can do a workaround and simulate certain scenarios with 'condition' and 'required' (and/or 'readonly') in the form.

" - }, - "switch", - { - "key": "email", - "condition": "model.switch", - "required": true - }, - { - "key": "email", - "condition": "!model.switch" - }, - { - "type": "submit", - "style": "btn-info", - "title": "OK" - } - ] -} diff --git a/examples/data/grid.json b/examples/data/grid.json deleted file mode 100644 index 125524597..000000000 --- a/examples/data/grid.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "schema": { - "type": "object", - "title": "Comment", - "properties": { - "name": { - "title": "Name", - "type": "string" - }, - "email": { - "title": "Email", - "type": "string", - "pattern": "^\\S+@\\S+$", - "description": "Email will be used for evil." - }, - "comment": { - "title": "Comment", - "type": "string", - "maxLength": 20, - "validationMessage": "Don't be greedy!" - } - }, - "required": ["name","email","comment"] - }, - "form": [ - { - "type": "help", - "helpvalue": "
Grid it up with bootstrap
" - }, - { - "type": "section", - "htmlClass": "row", - "items": [ - { - "type": "section", - "htmlClass": "col-xs-6", - "items": ["name"] - }, - { - "type": "section", - "htmlClass": "col-xs-6", - "items": ["email"] - } - ] - }, - { - "key": "comment", - "type": "textarea", - "placeholder": "Make a comment" - }, - { - "type": "submit", - "style": "btn-info", - "title": "OK" - } - ] -} diff --git a/examples/data/simple.json b/examples/data/simple.json deleted file mode 100644 index 42526df80..000000000 --- a/examples/data/simple.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "schema": { - "type": "object", - "title": "Comment", - "properties": { - "name": { - "title": "Name", - "type": "string" - }, - "email": { - "title": "Email", - "type": "string", - "pattern": "^\\S+@\\S+$", - "description": "Email will be used for evil." - }, - "comment": { - "title": "Comment", - "type": "string", - "maxLength": 20, - "validationMessage": "Don't be greedy!" - } - }, - "required": ["name","email","comment"] - }, - "form": [ - "name", - "email", - { - "key": "comment", - "type": "textarea", - "placeholder": "Make a comment" - }, - { - "type": "submit", - "style": "btn-info", - "title": "OK" - } - ] -} diff --git a/examples/data/sink.json b/examples/data/sink.json deleted file mode 100644 index fc1f3645b..000000000 --- a/examples/data/sink.json +++ /dev/null @@ -1,266 +0,0 @@ -{ - "schema": { - "type": "object", - "required": [ - "name", - "shoesizeLeft" - ], - "properties": { - "name": { - "title": "Name", - "description": "Gimme yea name lad", - "type": "string", - "pattern": "^[^/]*$", - "minLength": 2 - }, - "invitation": { - "type": "string", - "format": "html", - "title": "Invitation Design", - "description": "Design the invitation in full technicolor HTML" - }, - "favorite": { - "title": "Favorite", - "type": "string", - "enum": [ - "undefined", - "null", - "NaN" - ] - }, - "shoesizeLeft": { - "title": "Shoe size (left)", - "default": 42, - "type": "number" - }, - "shoesizeRight": { - "title": "Shoe size (right)", - "default": 42, - "type": "number" - }, - "attributes": { - "type": "object", - "title": "Attributes", - "required": [ - "eyecolor" - ], - "properties": { - "eyecolor": { - "type": "string", - "format": "color", - "title": "Eye color", - "default": "pink" - }, - "haircolor": { - "type": "string", - "title": "Hair color" - }, - "shoulders": { - "type": "object", - "title": "Shoulders", - "properties": { - "left": { - "type": "string", - "title": "Left" - }, - "right": { - "type": "string", - "title": "Right" - } - } - } - } - }, - "things": { - "type": "array", - "title": "I like...", - "items": { - "type": "string", - "enum": [ - "clowns", - "compiling", - "sleeping" - ] - } - }, - "dislike": { - "type": "array", - "title": "I dislike...", - "items": { - "type": "string", - "title": "I hate" - } - }, - "soul": { - "title": "Terms Of Service", - "description": "I agree to sell my undying soul", - "type": "boolean", - "default": true - }, - "soulserial": { - "title": "Soul Serial No", - "type": "string" - }, - "date": { - "title": "Date of party", - "type": "string", - "format": "date" - }, - "radio": { - "title": "Radio type", - "type": "string", - "enum": [ - "Transistor", - "Tube" - ] - }, - "radio2": { - "title": "My Second Radio", - "type": "string", - "enum": [ - "Transistor", - "Tube" - ] - }, - "radiobuttons": { - "type": "string", - "enum": [ - "Select me!", - "No me!" - ] - } - } - }, - "form": [ - { - "type": "fieldset", - "title": "Stuff", - "items": [ - { - "type": "tabs", - "tabs": [ - { - "title": "Simple stuff", - "items": [ - { - "key": "name", - "placeholder": "Check the console", - "onChange": "log(modelValue)", - "feedback": "{'glyphicon': true, 'glyphicon-ok': hasSuccess(), 'glyphicon-star': !hasSuccess() }" - }, - { - "key": "favorite", - "feedback": false - } - ] - }, - { - "title": "More stuff", - "items": [ - "attributes.eyecolor", - "attributes.haircolor", - { - "key": "attributes.shoulders.left", - "title": "Left shoulder", - "description": "This value is copied to attributes.shoulders.right in the model", - "copyValueTo": ["attributes.shoulders.right"] - }, - { - "key": "shoesizeLeft", - "feedback": false, - "copyValueTo":["shoesizeRight"] - }, - { - "key": "shoesizeRight" - }, - { - "key": "invitation", - "tinymceOptions": { - "toolbar": [ - "undo redo| styleselect | bold italic | link image", - "alignleft aligncenter alignright" - ] - } - }, - "things", - "dislike" - ] - } - ] - } - ] - }, - { - "type": "help", - "helpvalue": "
" - }, - "soul", - { - "type": "conditional", - "condition": "modelData.soul", - "items": [ - { - "key": "soulserial", - "placeholder": "ex. 666" - } - ] - }, - { - "key": "date", - "minDate": "2014-06-20" - }, - { - "key": "radio", - "type": "radios", - "titleMap": [ - { - "value": "Transistor", - "name": "Transistor
Not the tube kind." - }, - { - "value": "Tube", - "name": "Tube
The tube kind." - } - ] - }, - { - "key": "radio2", - "type": "radios-inline", - "titleMap": [ - { - "value": "Transistor", - "name": "Transistor
Not the tube kind." - }, - { - "value": "Tube", - "name": "Tube
The tube kind." - } - ] - }, - { - "key": "radiobuttons", - "style": { - "selected": "btn-success", - "unselected": "btn-default" - }, - "type": "radiobuttons", - "notitle": true - }, - { - "type": "actions", - "items": [ - { - "type": "submit", - "style": "btn-info", - "title": "Do It!" - }, - { - "type": "button", - "style": "btn-danger", - "title": "Noooooooooooo", - "onClick": "sayNo()" - } - ] - } - ] -} diff --git a/examples/data/tabarray.json b/examples/data/tabarray.json deleted file mode 100644 index 4be70ba64..000000000 --- a/examples/data/tabarray.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "schema": { - "type": "object", - "title": "Comment", - "properties": { - "comments": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "title": "Name", - "type": "string" - }, - "email": { - "title": "Email", - "type": "string", - "pattern": "^\\S+@\\S+$", - "description": "Email will be used for evil." - }, - "comment": { - "title": "Comment", - "type": "string", - "maxLength": 20, - "validationMessage": "Don't be greedy!" - } - }, - "required": ["name","email","comment"] - } - } - } - }, - "form": [ - { - "type": "help", - "helpvalue": "

Tabbed Array Example

Tab arrays can have tabs to the left, top or right.

" - }, - { - "key": "comments", - "type": "tabarray", - "add": "New", - "remove": "Delete", - "style": { - "remove": "btn-danger" - }, - "title": "{{ value.name || 'Tab '+$index }}", - "items": [ - "comments[].name", - "comments[].email", - { - "key": "comments[].comment", - "type": "textarea" - } - ] - }, - { - "type": "submit", - "style": "btn-default", - "title": "OK" - } - ] -} diff --git a/examples/data/titlemaps.json b/examples/data/titlemaps.json deleted file mode 100644 index e56cc5f38..000000000 --- a/examples/data/titlemaps.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "model": { - "select": "a", - "array": ["b"] - }, - "schema": { - "type": "object", - "properties": { - "select": { - "title": "Select without titleMap", - "type": "string", - "enum": ["a","b","c"] - }, - "select2": { - "title": "Select with titleMap (old style)", - "type": "string", - "enum": ["a","b","c"] - }, - "noenum": { "type": "string", "title": "No enum, but forms says it's a select" }, - "array": { - "title": "Array with enum defaults to 'checkboxes'", - "type": "array", - "items": { - "type": "string", - "enum": ["a","b","c"] - } - }, - "array2": { - "title": "Array with titleMap", - "type": "array", - "default": ["b","c"], - "items": { - "type": "string", - "enum": ["a","b","c"] - } - }, - "radios": { - "title": "Basic radio button example", - "type": "string", - "enum": ["a","b","c"] - }, - "radiobuttons": { - "title": "Radio buttons used to switch a boolean", - "type": "boolean", - "default": false - } - } - }, - "form": [ - "select", - { - "key": "select2", - "type": "select", - "titleMap": { - "a": "A", - "b": "B", - "c": "C" - } - }, - { - "key": "noenum", - "type": "select", - "titleMap": [ - { "value":"a", "name": "A" }, - { "value":"b", "name":"B" }, - { "value":"c", "name":"C" } - ] - }, - "array", - { - "key": "array2", - "type": "checkboxes", - "titleMap": [ - { "value":"a", "name": "A" }, - { "value":"b", "name":"B" }, - { "value":"c", "name":"C" } - ] - }, - { - "key": "radios", - "type": "radios", - "titleMap": [ - { "value":"c", "name": "C" }, - { "value":"b", "name":"B" }, - { "value":"a", "name":"A" } - ] - }, - { - "key":"radiobuttons", - "type": "radiobuttons", - "titleMap": [ - {"value": false, "name": "No way"}, - {"value": true, "name": "OK"} - ] - } - ] -} diff --git a/examples/data/types.json b/examples/data/types.json deleted file mode 100644 index ebe3c4938..000000000 --- a/examples/data/types.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "schema": { - "type": "object", - "title": "Types", - "properties": { - "string": { - "type": "string", - "minLength": 3 - }, - "integer": { - "type": "integer" - }, - "number": { - "type": "number" - }, - "boolean": { - "type": "boolean" - } - }, - "required": ["number"] - }, - "form": [ - "*", - {"type": "submit", "title": "OK"} - ] -} diff --git a/gulp/tasks/bootstrap-datepicker.js b/gulp/tasks/bootstrap-datepicker.js deleted file mode 100644 index c3b066e83..000000000 --- a/gulp/tasks/bootstrap-datepicker.js +++ /dev/null @@ -1,29 +0,0 @@ -var gulp = require('gulp'), - streamqueue = require('streamqueue'), - minifyHtml = require('gulp-minify-html'), - templateCache = require('gulp-angular-templatecache'), - concat = require('gulp-concat'), - uglify = require('gulp-uglify'); - -gulp.task('bootstrap-datepicker', function() { - var stream = streamqueue({objectMode: true}); - stream.queue( - gulp.src('./src/directives/decorators/bootstrap/datepicker/*.html') - .pipe(minifyHtml({ - empty: true, - spare: true, - quotes: true - })) - .pipe(templateCache({ - module: 'schemaForm', - root: 'directives/decorators/bootstrap/datepicker/' - })) - ); - stream.queue(gulp.src('./src/directives/decorators/bootstrap/datepicker/*.js')); - - stream.done() - .pipe(concat('bootstrap-datepicker.min.js')) - .pipe(uglify()) - .pipe(gulp.dest('./dist/')); - -}); \ No newline at end of file diff --git a/gulp/tasks/bootstrap.js b/gulp/tasks/bootstrap.js deleted file mode 100644 index d401239d6..000000000 --- a/gulp/tasks/bootstrap.js +++ /dev/null @@ -1,42 +0,0 @@ -var gulp = require('gulp'), - streamqueue = require('streamqueue'), - minifyHtml = require('gulp-minify-html'), - templateCache = require('gulp-angular-templatecache'), - concat = require('gulp-concat'), - rename = require('gulp-rename'), - umd = require('gulp-umd'), - uglify = require('gulp-uglify'); - -gulp.task('bootstrap', function() { - var stream = streamqueue({objectMode: true}); - stream.queue( - gulp.src('./src/directives/decorators/bootstrap/*.html') - .pipe(minifyHtml({ - empty: true, - spare: true, - quotes: true - })) - .pipe(templateCache({ - module: 'schemaForm', - root: 'directives/decorators/bootstrap/' - })) - ); - stream.queue(gulp.src('./src/directives/decorators/bootstrap/*.js')); - - stream.done() - .pipe(concat('bootstrap-decorator.js')) - .pipe(umd({ - dependencies: function() { - return [ - {name: 'schemaForm'}, - ]; - }, - exports: function() {return 'schemaForm';}, - namespace: function() {return 'bootstrapDecorator';} - })) - .pipe(gulp.dest('./dist/')) - .pipe(uglify()) - .pipe(rename('bootstrap-decorator.min.js')) - .pipe(gulp.dest('./dist/')); - -}); diff --git a/gulp/tasks/protractor.js b/gulp/tasks/protractor.js deleted file mode 100644 index 9a3bb1864..000000000 --- a/gulp/tasks/protractor.js +++ /dev/null @@ -1,38 +0,0 @@ -var gulp = require('gulp'); - -// The protractor task -var protractor = require('gulp-protractor'); - -// Start a standalone server -var webdriver_standalone = protractor.webdriver_standalone; - -// Download and update the selenium driver -var webdriver_update = protractor.webdriver_update; - -// Downloads the selenium webdriver -gulp.task('webdriver-update', webdriver_update); - -// Start the standalone selenium server -// NOTE: This is not needed if you reference the -// seleniumServerJar in your protractor.conf.js -gulp.task('webdriver-standalone', webdriver_standalone); - - -// Setting up the test task -gulp.task('protractor', ['webdriver-update'], function(cb) { - gulp.src(['test/protractor/specs/**/*.js']).pipe(protractor.protractor({ - configFile: 'test/protractor/conf.js', - })).on('error', function(e) { - console.log(e); - }).on('end', cb); -}); - -['validation-messages', 'custom-validation'].forEach(function(name) { - gulp.task('protractor:' + name, ['webdriver-update'], function(cb) { - gulp.src(['test/protractor/specs/' + name + '.js']).pipe(protractor.protractor({ - configFile: 'test/protractor/conf.js', - })).on('error', function(e) { - console.log(e); - }).on('end', cb); - }); -}); From 272e543cc7d18c77cb1d6ea4df7b0e02d0765fee Mon Sep 17 00:00:00 2001 From: David Jensen Date: Wed, 19 Aug 2015 10:49:31 +0200 Subject: [PATCH 03/23] Changed default task and deprecation notice --- gulp/tasks/default.js | 4 +--- src/services/decorators.js | 12 ++++++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/gulp/tasks/default.js b/gulp/tasks/default.js index b5ee304c8..8ea9789d3 100644 --- a/gulp/tasks/default.js +++ b/gulp/tasks/default.js @@ -1,7 +1,5 @@ var gulp = require('gulp'); gulp.task('default', [ - 'minify', - 'bootstrap', - 'bootstrap-datepicker' + 'minify' ]); diff --git a/src/services/decorators.js b/src/services/decorators.js index f7172d909..84d5ddbb5 100644 --- a/src/services/decorators.js +++ b/src/services/decorators.js @@ -19,8 +19,16 @@ angular.module('schemaForm').provider('schemaFormDecorators', return decorator['default'].template; }; - var createDirective = function(name) { - $compileProvider.directive(name, + /************************************************** + * DEPRECATED * + * The new builder and sf-field is preferred, but * + * we keep this in during a transitional period * + * so that add-ons that don't use the new builder * + * works. * + **************************************************/ + //TODO: Move to a compatability extra script. + var createDirective = function(name) { + $compileProvider.directive(name, ['$parse', '$compile', '$http', '$templateCache', '$interpolate', '$q', 'sfErrorMessage', 'sfPath','sfSelect', function($parse, $compile, $http, $templateCache, $interpolate, $q, sfErrorMessage, From 26f61b65ea22873b704dffb9d315ad1f2cd05a91 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Wed, 19 Aug 2015 11:12:16 +0200 Subject: [PATCH 04/23] Disclaimers and info about the transition. --- dist/WHERE_IS_BOOTSTRAP_DECORATOR.md | 15 ++ dist/schema-form.js | 12 +- docs/extending.md | 213 +-------------------- docs/index.md | 11 ++ src/directives/WHERE_ARE_THE_DECORATORS.md | 7 + 5 files changed, 46 insertions(+), 212 deletions(-) create mode 100644 dist/WHERE_IS_BOOTSTRAP_DECORATOR.md create mode 100644 src/directives/WHERE_ARE_THE_DECORATORS.md diff --git a/dist/WHERE_IS_BOOTSTRAP_DECORATOR.md b/dist/WHERE_IS_BOOTSTRAP_DECORATOR.md new file mode 100644 index 000000000..6c0da426c --- /dev/null +++ b/dist/WHERE_IS_BOOTSTRAP_DECORATOR.md @@ -0,0 +1,15 @@ +Hello, + +the `bootstrap-decorator.js` and `bootstrap-decorator.min.js` files has moved +to their project repo, https://github.com/Textalk/angular-schema-form-bootstrap + +You can install it via bower and npm as well. + +```sh +bower install angular-schema-form-bootstrap +``` +or + +```sh +npm install angular-schema-form-bootstrap +``` diff --git a/dist/schema-form.js b/dist/schema-form.js index 3e4387908..2c36d0a25 100644 --- a/dist/schema-form.js +++ b/dist/schema-form.js @@ -429,8 +429,16 @@ angular.module('schemaForm').provider('schemaFormDecorators', return decorator['default'].template; }; - var createDirective = function(name) { - $compileProvider.directive(name, + /************************************************** + * DEPRECATED * + * The new builder and sf-field is preferred, but * + * we keep this in during a transitional period * + * so that add-ons that don't use the new builder * + * works. * + **************************************************/ + //TODO: Move to a compatability extra script. + var createDirective = function(name) { + $compileProvider.directive(name, ['$parse', '$compile', '$http', '$templateCache', '$interpolate', '$q', 'sfErrorMessage', 'sfPath','sfSelect', function($parse, $compile, $http, $templateCache, $interpolate, $q, sfErrorMessage, diff --git a/docs/extending.md b/docs/extending.md index 873d156ee..c4e5fd035 100644 --- a/docs/extending.md +++ b/docs/extending.md @@ -1,214 +1,7 @@ Extending Schema Form ===================== -Schema Form is designed to be easily extended and there are two basic ways to do it: -1. Add a new type of field -2. Add a new decorator +Extending schema form with new form field via add-ons or changing to another CSS framework (a new +decorator) is easy. But how you do it has changed recently due to "the new builder". -Adding a Field --------------- -To add a new field to Schema Form you need to create a new form type and match that form type with -a template snippet. To do this you use the `schemaFormDecoratorsProvider.addMapping()` function. - -Ex. from the [datepicker add-on](https://github.com/Textalk/angular-schema-form-datepicker/blob/master/src/bootstrap-datepicker.js#L18) -```javascript - schemaFormDecoratorsProvider.addMapping( - 'bootstrapDecorator', - 'datepicker', - 'directives/decorators/bootstrap/datepicker/datepicker.html' -); -``` - -The second argument is the name of your new form type, in this case `datepicker`, and the third is -the template we bind to it (the first is the decorator, use `bootstrapDecorator` unless you know -what you are doing). - -What this means is that a form definition like this: -```javascript -$scope.form = [ - { - key: "birthday", - type: "datepicker" - } -]; -``` -...will result in the `datepicker.html` template to be used to render that field in the form. - -But wait, where is all the code? Basically it's then up to the template to use directives to -implement whatever it likes to do. It does have some help though, lets look at template example and -go through the basics. - -This is sort of the template for the datepicker: -```html -
- - - - - -
-``` - -### What's on the scope? -Each form field will be rendered inside a decorator directive, created by the -`schemaFormDecorators` factory service, *do* -[check the source](https://github.com/Textalk/angular-schema-form/blob/master/src/services/decorators.js#L33). - -This means you have several helper functions and values on scope, most important of this `form`. The -`form` variable contains the merged form definition for that field, i.e. your supplied form object + -the defaults from the schema (it also has its part of the schema under *form.schema*). -This is how you define and use new form field options, whatever is set on the form object is -available here for you to act on. - -| Name | What it does | -|----------|----------------| -| form | Form definition object | -| showTitle() | Shorthand for `form && form.notitle !== true && form.title` | -| ngModel | The ngModel controller, this will be on scope if you use either the directive `schema-validate` or `sf-array` | -| evalInScope(expr, locals) | Eval supplied expression, ie scope.$eval | -| evalExpr(expr, locals) | Eval an expression in the parent scope of the main `sf-schema` directive. | -| interp(expr, locals) | Interpolate an expression which may or may not contain expression `{{ }}` sequences | -| buttonClick($event, form) | Use this with ng-click to execute form.onClick | -| hasSuccess() | Shorthand for `ngModel.$valid && (!ngModel.$pristine || !ngModel.$isEmpty(ngModel.$modelValue))` | -| hasError() | Shorthand for `ngModel.$invalid && !ngModel.$pristine` | - -#### Deprecation warning -There is still a `errorMessage` function on scope but it's been deprecated. Please use the -`sf-message` directive instead. - - -### The magic $$value$$ -Schema Form wants to play nice with the built in Angular directives for form. Especially `ng-model` -which we want to handle the two way binding against our model value. Also by using `ng-model` we -get all the nice validation states from the `ngModelController` and `FormController` that we all -know and love. - -To get that working properly we had to resort to a bit of trickery, right before we let Angular -compile the field template we do a simple string replacement of `$$value$$` and replace that -with the path to the current form field on the model, i.e. `form.key`. - -So `ng-model="$$value$$"` becomes something like `ng-model="model['person']['address']['street']"`, -you can see this if you inspect the final form in the browser. - -So basically always have a `ng-model="$$value$$"` (Pro tip: ng-model is fine on any element, put - it on the same div as your custom directive and require the ngModelController for full control). - -### schema-validate directive -`schema-validate` is a directive that you should put on the same element as your `ng-model`. It is -responsible for validating the value against the schema using [tv4js](https://github.com/geraintluff/tv4) -It takes the form definition as an argument. - - -### sf-message directive -Error messages are nice, and the best way to get them is via the `sf-message` directive. It usually -takes `form.description` as an argument so it can show that until an error occurs. - - -### Setting up schema defaults -So you got this shiny new add-on that adds a fancy field type, but feel a bit bummed out that you -need to specify it in the form definition all the time? Fear not because you can also add a "rule" -to map certain types and conditions in the schema to default to your type. - -You do this by adding to the `schemaFormProvider.defaults` object. The `schemaFormProvider.defaults` -is an object with a key for each type *in JSON Schema* with a array of functions as its value. - -```javscript -var defaults = { - string: [], - object: [], - number: [], - integer: [], - boolean: [], - array: [] -}; -``` - -When schema form traverses the JSON Schema to create default form definitions it first checks the -*JSON Schema type* and then calls on each function in the corresponding list *in order* until a -function actually returns something. That is then used as a defualt. - -This is the function that makes it a datepicker if its a string and has format "date" or "date-time": - -```javascript -var datepicker = function(name, schema, options) { - if (schema.type === 'string' && (schema.format === 'date' || schema.format === 'date-time')) { - var f = schemaFormProvider.stdFormObj(name, schema, options); - f.key = options.path; - f.type = 'datepicker'; - options.lookup[sfPathProvider.stringify(options.path)] = f; - return f; - } -}; - -// Put it first in the list of functions -schemaFormProvider.defaults.string.unshift(datepicker); -``` - -### Sharing your add-on with the world -So you made an add-on, why not share it with us? On the front page, -[http://textalk.github.io/angular-schema-form/](http://textalk.github.io/angular-schema-form/#third-party-addons), we -maintain a list of add ons based on a query of the bower register, and we love to see your add-on -there. - -Any [bower](http://bower.io) package with a name starting with `angular-schema-form-` or that has -the `keyword` `angular-schema-form-add-on` in its `bower.json` will be picked up. It's cached so -there can be a delay of a day or so. - -So [make a bower package](http://bower.io/docs/creating-packages/), add the keyword -`angular-schema-form-add-on` and [register it](http://bower.io/docs/creating-packages/#register)! - -Decorators ----------- -Decorators are a second way to extend Schema Form, the thought being that you should easily be able -to change *every* field. Maybe you like it old school and want to use bootstrap 2. Or maybe you like -to generate a table with the data instead? Right now there are no other decorators than bootstrap 3. - -Basically a *decorator* sets up all the mappings between form types and their respective templates -using the `schemaFormDecoratorsProvider.createDecorator()` function. - -```javascript -var base = 'directives/decorators/bootstrap/'; - -schemaFormDecoratorsProvider.createDecorator('bootstrapDecorator', { - textarea: base + 'textarea.html', - fieldset: base + 'fieldset.html', - array: base + 'array.html', - tabarray: base + 'tabarray.html', - tabs: base + 'tabs.html', - section: base + 'section.html', - conditional: base + 'section.html', - actions: base + 'actions.html', - select: base + 'select.html', - checkbox: base + 'checkbox.html', - checkboxes: base + 'checkboxes.html', - number: base + 'default.html', - password: base + 'default.html', - submit: base + 'submit.html', - button: base + 'submit.html', - radios: base + 'radios.html', - 'radios-inline': base + 'radios-inline.html', - radiobuttons: base + 'radio-buttons.html', - help: base + 'help.html', - 'default': base + 'default.html' -}, [ - function(form) { - if (form.readonly && form.key && form.type !== 'fieldset') { - return base + 'readonly.html'; - } - } -]); -``` -`schemaFormDecoratorsProvider.createDecorator(name, mapping, rules)` takes a name argument, a mapping object -(type -> template) and an optional list of rule functions. - -When the decorator is trying to match a form type against a tempate it first executes all the rules -in order. If one returns that is used as template, otherwise it checks the mappings. +New documentation is in the works and will come soon. diff --git a/docs/index.md b/docs/index.md index 40db0478c..35e2d2a39 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,3 +1,14 @@ + +IMPORTANT +========= + +**Angular Schema Form is undergoing a refactoring and the "bootstrap decorator", i.e. the part with +all the HTML has been moved to [github.com/Textalk/angular-schema-form-bootstrap](https://github.com/Textalk/angular-schema-form-bootstrap).** + +The documentation below, especially form options is therefore somwhat bootstrap decorator +specific. The docs is undergoing updating. + + Documentation ============= diff --git a/src/directives/WHERE_ARE_THE_DECORATORS.md b/src/directives/WHERE_ARE_THE_DECORATORS.md new file mode 100644 index 000000000..67fb9d32f --- /dev/null +++ b/src/directives/WHERE_ARE_THE_DECORATORS.md @@ -0,0 +1,7 @@ +Hi there, + +as you probably have noticed the `decorators` folder and the bootstrap decorator has +been removed. This is intentional. Each decorator has it's own repo. + +Material: https://github.com/Textalk/angular-schema-form-material +Bootstrap: https://github.com/Textalk/angular-schema-form-bootstrap From cca159304ad39235fc957ec6c46a62a7fd6d58a2 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Wed, 19 Aug 2015 11:16:18 +0200 Subject: [PATCH 05/23] Updated the contributing.md with a warning --- CONTRIBUTING.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c02c039a7..f03d09824 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,6 +11,11 @@ request heck of a lot easier for us. Please avoid including anything from the `dist/` directory as that can make merging harder, and we always generate these files when we make a new release. +** We're currently in transitioning where a large part of the code, i.e. the bootstrap decorator, +has been moved to it's own repo. It's here [github.com/Textalk/angular-schema-form-bootstrap](https://github.com/Textalk/angular-schema-form-bootstrap)* + +Feel free to submit issues on the main repo anyway though. + If its a new field type consider making it an add-on instead, especially if it has external dependencies. See [extending Schema Form documentation.](docs/extending.md) From 4a89debf08287be5db15bee5119deef88d9cf4ed Mon Sep 17 00:00:00 2001 From: David Jensen Date: Thu, 20 Aug 2015 09:41:29 +0200 Subject: [PATCH 06/23] Tests now use the new bootstrap decorator --- bower.json | 3 +- karma.conf.js | 15 +- package.json | 1 - test/directives/schema-form-test.js | 327 ++++++++----------- test/directives/sf-messages-test.js | 1 - test/protractor/conf.js | 3 - test/protractor/specs/custom-validation.js | 74 ----- test/protractor/specs/validation-messages.js | 66 ---- 8 files changed, 135 insertions(+), 355 deletions(-) delete mode 100644 test/protractor/conf.js delete mode 100644 test/protractor/specs/custom-validation.js delete mode 100644 test/protractor/specs/validation-messages.js diff --git a/bower.json b/bower.json index 312c2f6a1..50dca085a 100644 --- a/bower.json +++ b/bower.json @@ -52,6 +52,7 @@ "angular-mocks": ">= 1.2", "tx-tinymce": ">= 0.0.5", "angular-ui-sortable": ">=0.12.11", - "bootstrap-vertical-tabs": "~1.2.0" + "bootstrap-vertical-tabs": "~1.2.0", + "angular-schema-form-bootstrap": "~0.1.1" } } diff --git a/karma.conf.js b/karma.conf.js index 729a95568..40080b914 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -23,8 +23,7 @@ module.exports = function(config) { 'src/sfPath.js', 'src/services/*.js', 'src/directives/*.js', - 'src/directives/decorators/bootstrap/*.js', - 'src/**/*.html', + 'bower_components/angular-schema-form-bootstrap/bootstrap-decorator.js', 'test/services/schema-form-test.js', 'test/services/decorators-test.js', 'test/services/messages-test.js', @@ -43,8 +42,7 @@ module.exports = function(config) { reporters: ['progress','coverage','growler'], preprocessors: { - 'src/**/*.js': ['coverage'], - 'src/**/*.html': ['ng-html2js'] + 'src/**/*.js': ['coverage'] }, // optionally, configure the reporter @@ -53,15 +51,6 @@ module.exports = function(config) { dir : 'coverage/' }, - - ngHtml2JsPreprocessor: { - cacheIdFromPath: function(filepath) { - return filepath.substr(4); - }, - moduleName: 'templates' - }, - - // web server port port: 9876, diff --git a/package.json b/package.json index cfd301321..104185338 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "karma-coverage": "^0.2.1", "karma-growler-reporter": "0.0.1", "karma-mocha": "^0.1.3", - "karma-ng-html2js-preprocessor": "^0.1.0", "karma-phantomjs-launcher": "^0.1.4", "mocha": "^1.18.0", "mocha-lcov-reporter": "0.0.1", diff --git a/test/directives/schema-form-test.js b/test/directives/schema-form-test.js index 3f47762fb..6ba413aa0 100644 --- a/test/directives/schema-form-test.js +++ b/test/directives/schema-form-test.js @@ -1,7 +1,6 @@ chai.should(); describe('directive',function(){ - beforeEach(module('templates')); beforeEach(module('schemaForm')); beforeEach( //We don't need no sanitation. We don't need no thought control. @@ -46,12 +45,11 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.equal(2); - tmpl.children().eq(0).is('bootstrap-decorator').should.be.true; - tmpl.children().eq(0).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').is('input[type="text"]').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').attr('ng-model').should.be.equal('model[\'name\']'); - tmpl.children().eq(1).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(1).children().eq(0).children('select').length.should.equal(1); + tmpl.children().eq(0).is('div.form-group').should.be.true; + tmpl.children().eq(0).find('input').is('input[type="text"]').should.be.true; + tmpl.children().eq(0).find('input').attr('ng-model').should.be.equal('model[\'name\']'); + tmpl.children().eq(0).is('div.form-group').should.be.true; + tmpl.children().eq(1).children('select').length.should.equal(1); }); }); @@ -72,12 +70,11 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.equal(2); - tmpl.children().eq(0).is('bootstrap-decorator').should.be.true; - tmpl.children().eq(0).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').is('input[type="text"]').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').attr('ng-model').should.be.equal('model[\'name\']'); - tmpl.children().eq(1).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(1).children().eq(0).children('select').length.should.equal(1); + tmpl.children().eq(0).is('div.form-group').should.be.true; + tmpl.children().eq(0).find('input').is('input[type="text"]').should.be.true; + tmpl.children().eq(0).find('input').attr('ng-model').should.be.equal('model[\'name\']'); + tmpl.children().eq(1).is('div.form-group').should.be.true; + tmpl.children().eq(1).children('select').length.should.equal(1); }); }); @@ -98,11 +95,11 @@ describe('directive',function(){ }, 'ianal': { 'type': 'boolean', - 'title':'IANAL' + 'title': 'IANAL' }, 'age': { 'type': 'integer', - 'title':'Age', + 'title': 'Age', 'minimum': 0 }, 'sum': { @@ -147,30 +144,30 @@ describe('directive',function(){ tmpl.children().length.should.be.equal(6); - tmpl.children().eq(0).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').is('input[type="text"]').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').attr('ng-model').should.be.equal('model[\'name\']'); + tmpl.children().eq(0).is('div.form-group').should.be.true; + tmpl.children().eq(0).find('input').is('input[type="text"]').should.be.true; + tmpl.children().eq(0).find('input').attr('ng-model').should.be.equal('model[\'name\']'); + + tmpl.children().eq(1).is('div.checkbox').should.be.true; + tmpl.children().eq(1).find('input[type="checkbox"]').length.should.be.eq(1); - tmpl.children().eq(1).children().eq(0).is('div.checkbox').should.be.true; - tmpl.children().eq(1).children().eq(0).find('input[type="checkbox"]').length.should.be.eq(1); + tmpl.children().eq(2).is('div.form-group').should.be.true; + tmpl.children().eq(2).find('input[type="number"]').length.should.be.eq(1); - tmpl.children().eq(2).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(2).children().eq(0).find('input[type="number"]').length.should.be.eq(1); + tmpl.children().eq(3).is('div.form-group').should.be.true; + tmpl.children().eq(3).find('input[type="number"]').length.should.be.eq(1); - tmpl.children().eq(3).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(3).children().eq(0).find('input[type="number"]').length.should.be.eq(1); + tmpl.children().eq(4).is('div.form-group').should.be.true; + tmpl.children().eq(4).find('select').length.should.be.eq(1); - tmpl.children().eq(4).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(4).children().eq(0).find('select').length.should.be.eq(1); + tmpl.children().eq(5).is('fieldset').should.be.true; + tmpl.children().eq(5).children().eq(0).is('legend').should.be.true; - tmpl.children().eq(5).children().eq(0).is('fieldset').should.be.true; - tmpl.children().eq(5).children().eq(0).children().eq(0).is('legend').should.be.true; - tmpl.children().eq(5).children().eq(0).children().eq(3).is('sf-decorator').should.be.true; + tmpl.children().eq(5).children().eq(4).is('fieldset').should.be.true; + tmpl.children().eq(5).children().eq(4).children().length.should.be.eq(4); - tmpl.children().eq(5).children().eq(0).children().eq(4).children().eq(0).is('fieldset').should.be.true; - tmpl.children().eq(5).children().eq(0).children().eq(4).children().eq(0).children().length.should.be.eq(4); - tmpl.children().eq(5).children().eq(0).children().eq(4).children().eq(0).find('input[ng-model="model[\'attributes\'][\'shoulders\'][\'left\']"]').length.should.be.eq(1); - tmpl.children().eq(5).children().eq(0).children().eq(4).children().eq(0).find('input[ng-model="model[\'attributes\'][\'shoulders\'][\'right\']"]').length.should.be.eq(1); + tmpl.children().eq(5).children().eq(4).find('input[ng-model="model[\'attributes\'][\'shoulders\'][\'left\']"]').length.should.be.eq(1); + tmpl.children().eq(5).children().eq(4).find('input[ng-model="model[\'attributes\'][\'shoulders\'][\'right\']"]').length.should.be.eq(1); }); }); @@ -189,12 +186,12 @@ describe('directive',function(){ $compile(tmpl)(scope); $rootScope.$apply(); + tmpl.children().length.should.be.equal(2); tmpl.children().eq(0).is('input[type="text"]').should.be.true; tmpl.children().eq(0).attr('ng-model').should.be.equal('person.name'); - tmpl.children().eq(1).is('bootstrap-decorator').should.be.true; - tmpl.children().eq(1).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(1).children().eq(0).children('select').length.should.equal(1); + tmpl.children().eq(1).is('div.form-group').should.be.true; + tmpl.children().eq(1).children('select').length.should.equal(1); }); }); @@ -246,12 +243,12 @@ describe('directive',function(){ $compile(tmpl)(scope); $rootScope.$apply(); tmpl.children().length.should.be.equal(3); - tmpl.children().eq(0).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').is('input[type="text"]').should.be.true; - tmpl.children().eq(1).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(1).children().eq(0).children('select').length.should.equal(1); - tmpl.children().eq(2).children().eq(0).find('input').is('input[type=submit]').should.be.true; - tmpl.children().eq(2).children().eq(0).find('input').val().should.be.equal('Okidoki'); + tmpl.children().eq(0).is('div.form-group').should.be.true; + tmpl.children().eq(0).find('input').is('input[type="text"]').should.be.true; + tmpl.children().eq(1).is('div.form-group').should.be.true; + tmpl.children().eq(1).children('select').length.should.equal(1); + tmpl.children().eq(2).find('input').is('input[type=submit]').should.be.true; + tmpl.children().eq(2).find('input').val().should.be.equal('Okidoki'); }); }); @@ -271,12 +268,12 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.equal(3); - tmpl.children().eq(0).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').is('input[type="text"]').should.be.true; - tmpl.children().eq(1).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(1).children().eq(0).children('select').length.should.equal(1); - tmpl.children().eq(2).children().eq(0).find('button').length.should.be.equal(1); - tmpl.children().eq(2).children().eq(0).find('button').text().should.include('Okidoki'); + tmpl.children().eq(0).is('div.form-group').should.be.true; + tmpl.children().eq(0).find('input').is('input[type="text"]').should.be.true; + tmpl.children().eq(1).is('div.form-group').should.be.true; + tmpl.children().eq(1).children('select').length.should.equal(1); + tmpl.children().eq(2).find('button').length.should.be.equal(1); + tmpl.children().eq(2).find('button').text().should.include('Okidoki'); scope.form[1].onClick.should.not.have.beenCalled; tmpl.children().eq(2).children().eq(0).find('button').click(); @@ -356,12 +353,12 @@ describe('directive',function(){ $compile(tmpl)(scope); $rootScope.$apply(); tmpl.children().length.should.be.equal(2); - tmpl.children().eq(0).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').is('input[type="text"]').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').attr('disabled').should.be.equal('disabled'); - tmpl.children().eq(1).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(1).children().eq(0).children('input').length.should.equal(1); - expect(tmpl.children().eq(1).children().eq(0).children('input').attr('disabled')).to.be.undefined; + tmpl.children().eq(0).is('div.form-group').should.be.true; + tmpl.children().eq(0).find('input').is('input[type="text"]').should.be.true; + tmpl.children().eq(0).find('input').attr('disabled').should.be.equal('disabled'); + tmpl.children().eq(1).is('div.form-group').should.be.true; + tmpl.children().eq(1).children('input').length.should.equal(1); + expect(tmpl.children().eq(1).children('input').attr('disabled')).to.be.undefined; }); }); @@ -387,12 +384,12 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.equal(2); - tmpl.children().eq(0).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').is('input[type="text"]').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').attr('disabled').should.be.equal('disabled'); - tmpl.children().eq(1).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(1).children().eq(0).children('input').length.should.equal(1); - expect(tmpl.children().eq(1).children().eq(0).children('input').attr('disabled')).to.be.undefined; + tmpl.children().eq(0).is('div.form-group').should.be.true; + tmpl.children().eq(0).find('input').is('input[type="text"]').should.be.true; + tmpl.children().eq(0).find('input').attr('disabled').should.be.equal('disabled'); + tmpl.children().eq(1).is('div.form-group').should.be.true; + tmpl.children().eq(1).children('input').length.should.equal(1); + expect(tmpl.children().eq(1).children('input').attr('disabled')).to.be.undefined; }); }); @@ -421,12 +418,12 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.equal(2); - tmpl.children().eq(0).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').is('input[type="text"]').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').attr('disabled').should.be.equal('disabled'); - tmpl.children().eq(1).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(1).children().eq(0).children('input').length.should.equal(1); - expect(tmpl.children().eq(1).children().eq(0).children('input').attr('disabled')).to.be.undefined; + tmpl.children().eq(0).is('div.form-group').should.be.true; + tmpl.children().eq(0).find('input').is('input[type="text"]').should.be.true; + tmpl.children().eq(0).find('input').attr('disabled').should.be.equal('disabled'); + tmpl.children().eq(1).is('div.form-group').should.be.true; + tmpl.children().eq(1).children('input').length.should.equal(1); + expect(tmpl.children().eq(1).children('input').attr('disabled')).to.be.undefined; }); }); @@ -668,7 +665,7 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.eq(1); - var labels = tmpl.children().children().find('label'); + var labels = tmpl.children().find('label'); labels.eq(0).text().should.equal('Nick'); labels.eq(1).text().should.equal('Name'); labels.eq(2).text().should.equal('Alias'); @@ -718,7 +715,6 @@ describe('directive',function(){ $compile(tmpl)(scope); $rootScope.$apply(); - scope.person.arr[0].name.should.be.equal('Name'); scope.person.arr[0].nick.should.be.equal('Nick'); expect(scope.person.arr[0].alias).to.be.undefined; @@ -748,8 +744,8 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.equal(2); - tmpl.children().eq(0).children().eq(0).find('label').hasClass('sr-only').should.be.true; - tmpl.children().eq(1).children().eq(0).find('label').hasClass('ng-hide').should.be.true; + tmpl.children().eq(0).eq(0).find('label').hasClass('sr-only').should.be.true; + tmpl.children().eq(1).eq(0).find('label').hasClass('ng-hide').should.be.true; }); }); @@ -783,7 +779,7 @@ describe('directive',function(){ //TODO: more asserts tmpl.children().length.should.be.equal(2); - tmpl.children().eq(0).children().eq(0).find('input[type=checkbox]').length.should.be.eq(2); + tmpl.children().eq(0).find('input[type=checkbox]').length.should.be.eq(2); }); }); @@ -853,10 +849,10 @@ describe('directive',function(){ //TODO: more asserts tmpl.children().length.should.be.equal(2); - tmpl.children().eq(0).children().eq(0).find('input[type=radio]').length.should.be.eq(2); - tmpl.children().eq(0).children().eq(0).find('.radio').length.should.be.eq(2); - tmpl.children().eq(1).children().eq(0).find('input[type=radio]').length.should.be.eq(2); - tmpl.children().eq(1).children().eq(0).find('.btn').length.should.be.eq(2); + tmpl.children().eq(0).find('input[type=radio]').length.should.be.eq(2); + tmpl.children().eq(0).find('.radio').length.should.be.eq(2); + tmpl.children().eq(1).find('input[type=radio]').length.should.be.eq(2); + tmpl.children().eq(1).find('.btn').length.should.be.eq(2); }); }); @@ -1083,54 +1079,9 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.equal(1); - tmpl.children().eq(0).children().eq(0).is('div').should.be.true; - tmpl.children().eq(0).children().eq(0).hasClass('btn-group').should.be.false; - tmpl.children().eq(0).children().eq(0).children().length.should.be.eq(2); - }); - }); - - it('should handle a simple div with a condition if "conditional" is specified',function(done){ - - inject(function($compile,$rootScope){ - var scope = $rootScope.$new(); - scope.person = { show: false }; - - scope.schema = exampleSchema; - - scope.form = [{ - type: "conditional", - condition: "person.show", - items: [ - { - key: 'name', - notitle: true - }, - { - key: 'gender', - notitle: true - } - ] - }]; - - var tmpl = angular.element('
'); - - $compile(tmpl)(scope); - $rootScope.$apply(); - - tmpl.children().length.should.be.equal(1); - tmpl.children().eq(0).children().length.should.be.equal(0); - - //Do a setTimeout so we kan do another $apply - setTimeout(function(){ - scope.person.show = true; - scope.$apply(); - tmpl.children().length.should.be.equal(1); - tmpl.children().eq(0).children().eq(0).is('div').should.be.true; - tmpl.children().eq(0).children().eq(0).hasClass('btn-group').should.be.false; - tmpl.children().eq(0).children().eq(0).children().length.should.be.eq(2); - done(); - },10); - + tmpl.children().eq(0).is('div').should.be.true; + tmpl.children().eq(0).hasClass('btn-group').should.be.false; + tmpl.children().eq(0).children().length.should.be.eq(2); }); }); @@ -1162,11 +1113,11 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.equal(1); - tmpl.children().eq(0).children().eq(0).is('div').should.be.true; - tmpl.children().eq(0).children().eq(0).hasClass('btn-group').should.be.true; - tmpl.children().eq(0).children().eq(0).children().length.should.be.eq(2); - tmpl.children().eq(0).children().eq(0).children().eq(0).is('input').should.be.true; - tmpl.children().eq(0).children().eq(0).children().eq(1).is('button').should.be.true; + tmpl.children().eq(0).is('div').should.be.true; + tmpl.children().eq(0).hasClass('btn-group').should.be.true; + tmpl.children().eq(0).children().length.should.be.eq(2); + tmpl.children().eq(0).children().eq(0).is('input').should.be.true; + tmpl.children().eq(0).children().eq(1).is('button').should.be.true; }); }); @@ -1208,14 +1159,13 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.equal(1); - tmpl.children().eq(0).children().length.should.be.equal(1); - tmpl.children().eq(0).children().eq(0).children().length.should.be.eq(4); - tmpl.children().eq(0).children().eq(0).children().eq(0).hasClass('btn-success').should.be.false; - tmpl.children().eq(0).children().eq(0).children().eq(1).hasClass('btn-default').should.be.true; - tmpl.children().eq(0).children().eq(0).children().eq(1).hasClass('btn-danger').should.be.false; - tmpl.children().eq(0).children().eq(0).children().eq(2).hasClass('btn-success').should.be.true; - tmpl.children().eq(0).children().eq(0).children().eq(3).hasClass('btn-default').should.be.false; - tmpl.children().eq(0).children().eq(0).children().eq(3).hasClass('btn-danger').should.be.true; + tmpl.children().eq(0).children().length.should.be.eq(4); + tmpl.children().eq(0).children().eq(0).hasClass('btn-success').should.be.false; + tmpl.children().eq(0).children().eq(1).hasClass('btn-default').should.be.true; + tmpl.children().eq(0).children().eq(1).hasClass('btn-danger').should.be.false; + tmpl.children().eq(0).children().eq(2).hasClass('btn-success').should.be.true; + tmpl.children().eq(0).children().eq(3).hasClass('btn-default').should.be.false; + tmpl.children().eq(0).children().eq(3).hasClass('btn-danger').should.be.true; }); }); @@ -1255,9 +1205,9 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.eq(2); - tmpl.children().eq(0).children().eq(0).is('div').should.be.true; - tmpl.children().eq(0).children().eq(0).children().length.should.eq(1); - tmpl.children().eq(0).children().eq(0).children().html().should.be.eq("Yo Ninja!"); + tmpl.children().eq(0).is('div').should.be.true; + tmpl.children().eq(0).children().length.should.eq(1); + tmpl.children().eq(0).children().html().should.be.eq("Yo Ninja!"); }); }); @@ -1306,8 +1256,8 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.eq(1); - var tabs = tmpl.children().children().eq(0).children().eq(0); - var panes = tmpl.children().children().eq(0).children().eq(1); + var tabs = tmpl.children().children().eq(0); + var panes = tmpl.children().children().eq(1); tabs.is('ul').should.be.true; tabs.children().length.should.be.eq(2); @@ -1394,19 +1344,19 @@ describe('directive',function(){ //TODO: more asserts tmpl.children().length.should.be.equal(3); - tmpl.children().eq(0).children().eq(0).find('input').length.should.be.eq(1); - tmpl.children().eq(0).children().eq(0).find('button').length.should.be.eq(2); - tmpl.children().eq(0).children().eq(0).find('button').eq(1).text().trim().should.be.eq('Add'); - - tmpl.children().eq(1).children().eq(0).find('input').length.should.be.eq(1); - tmpl.children().eq(1).children().eq(0).find('fieldset').length.should.be.eq(0); - tmpl.children().eq(1).children().eq(0).find('button').length.should.be.eq(2); - tmpl.children().eq(1).children().eq(0).find('button').eq(1).text().trim().should.be.eq('Add'); - - tmpl.children().eq(2).children().eq(0).find('input').length.should.be.eq(2); - tmpl.children().eq(2).children().eq(0).find('fieldset').length.should.be.eq(1); - tmpl.children().eq(2).children().eq(0).find('button').length.should.be.eq(4); - tmpl.children().eq(2).children().eq(0).find('button').eq(3).text().trim().should.be.eq('Add'); + tmpl.children().eq(0).find('input').length.should.be.eq(1); + tmpl.children().eq(0).find('button').length.should.be.eq(2); + tmpl.children().eq(0).find('button').eq(1).text().trim().should.be.eq('Add'); + + tmpl.children().eq(1).find('input').length.should.be.eq(1); + tmpl.children().eq(1).find('fieldset').length.should.be.eq(0); + tmpl.children().eq(1).find('button').length.should.be.eq(2); + tmpl.children().eq(1).find('button').eq(1).text().trim().should.be.eq('Add'); + + tmpl.children().eq(2).find('input').length.should.be.eq(2); + tmpl.children().eq(2).find('fieldset').length.should.be.eq(1); + tmpl.children().eq(2).find('button').length.should.be.eq(4); + tmpl.children().eq(2).find('button').eq(3).text().trim().should.be.eq('Add'); }); @@ -1546,21 +1496,17 @@ describe('directive',function(){ //TODO: more asserts tmpl.children().length.should.be.equal(2); - tmpl.children().eq(0).children().eq(0).find('input').length.should.be.eq(3); - tmpl.children().eq(0).children().eq(0).find('button').length.should.be.eq(3); - tmpl.children().eq(0).children().eq(0).find('button').eq(0).text().trim().should.be.eq('Remove'); - tmpl.children().eq(0).children().eq(0).is('div').should.be.true; - tmpl.children().eq(0).children().eq(0).attr('sf-array').should.be.thruthy; - tmpl.children().eq(0).children().eq(0).find('.tabs-left').length.should.be.eq(1); - - tmpl.children().eq(1).children().eq(0).find('input').length.should.be.eq(1); - tmpl.children().eq(1).children().eq(0).find('fieldset').length.should.be.eq(0); - tmpl.children().eq(1).children().eq(0).find('button').length.should.be.eq(1); - tmpl.children().eq(1).children().eq(0).find('button').text().trim().should.be.eq('Remove'); - tmpl.children().eq(1).children().eq(0).attr('sf-array').should.be.thruthy; - tmpl.children().eq(1).children().eq(0).find('.tabs-left').length.should.be.eq(0); - tmpl.children().eq(1).children().eq(0).find('.tabs-right').length.should.be.eq(1); - + tmpl.children().eq(0).find('input').length.should.be.eq(3); + tmpl.children().eq(0).find('button').length.should.be.eq(3); + tmpl.children().eq(0).find('button').eq(0).text().trim().should.be.eq('Remove'); + tmpl.children().eq(0).is('div').should.be.true; + tmpl.children().eq(0).find('.tabs-left').length.should.be.eq(1); + + tmpl.children().eq(1).find('input').length.should.be.eq(1); + tmpl.children().eq(1).find('fieldset').length.should.be.eq(0); + tmpl.children().eq(1).find('button').length.should.be.eq(1); + tmpl.children().eq(1).find('button').text().trim().should.be.eq('Remove'); + tmpl.children().eq(1).find('.tabs-left').length.should.be.eq(0); }); }); @@ -1628,7 +1574,7 @@ describe('directive',function(){ tmpl.children().eq(1).find('button').text().trim().should.be.eq('Delete'); tmpl.children().eq(1).find('button').eq(0).hasClass('btn-default').should.be.true; tmpl.children().eq(1).find('button').eq(0).hasClass('btn-danger').should.be.false; - tmpl.children().eq(1).find('li:not([ng-repeat]) > a').text().trim().should.be.eq('Add'); + }); }); @@ -1772,13 +1718,13 @@ describe('directive',function(){ $compile(tmpl)(scope); $rootScope.$apply(); - - tmpl.children().find('.schema-form-text').length.should.be.equal(1); + tmpl.find('.schema-form-text').length.should.be.equal(1); setTimeout(function() { + scope.person.flag = false; $rootScope.$apply(); - tmpl.children().find('.schema-form-text').length.should.be.equal(0); + tmpl.find('.schema-form-text').length.should.be.equal(0); done(); }, 0); @@ -1808,21 +1754,21 @@ describe('directive',function(){ $compile(tmpl)(scope); $rootScope.$apply(); - tmpl.children().find('.schema-form-text').length.should.be.equal(1); - tmpl.children().find('.schema-form-textarea').length.should.be.equal(0); + tmpl.find('.schema-form-text').length.should.be.equal(1); + tmpl.find('.schema-form-textarea').length.should.be.equal(0); setTimeout(function() { scope.form[0].type = 'textarea'; scope.$broadcast('schemaFormRedraw'); $rootScope.$apply(); - tmpl.children().find('.schema-form-text').length.should.be.equal(0); + tmpl.find('.schema-form-text').length.should.be.equal(0); done(); }, 0); }); }); - it('should use supplied template with template field type',function() { + it.skip('should use supplied template with template field type',function() { inject(function($compile, $rootScope){ var scope = $rootScope.$new(); @@ -1847,13 +1793,12 @@ describe('directive',function(){ $compile(tmpl)(scope); $rootScope.$apply(); - tmpl.children().eq(0).html().should.be.eq('
Hello World
') }); }); - it('should load template by templateUrl, with template field type',function() { + it.skip('should load template by templateUrl, with template field type',function() { inject(function($compile, $rootScope, $httpBackend){ @@ -2026,10 +1971,10 @@ describe('directive',function(){ ngModelCtrl.$valid = true; ngModelCtrl.$pristine = false; $rootScope.$apply(); - tmpl.children().eq(0).children().eq(0).hasClass('has-success').should.be.true; + tmpl.children().eq(0).hasClass('has-success').should.be.true; scope.form[0].disableSuccessState = true; $rootScope.$apply(); - tmpl.children().eq(0).children().eq(0).hasClass('has-success').should.be.false; + tmpl.children().eq(0).hasClass('has-success').should.be.false; }); }); @@ -2054,10 +1999,10 @@ describe('directive',function(){ ngModelCtrl.$invalid = true; ngModelCtrl.$pristine = false; $rootScope.$apply(); - tmpl.children().eq(0).children().eq(0).hasClass('has-error').should.be.true; + tmpl.children().eq(0).hasClass('has-error').should.be.true; scope.form[0].disableErrorState = true; $rootScope.$apply(); - tmpl.children().eq(0).children().eq(0).hasClass('has-error').should.be.false; + tmpl.children().eq(0).hasClass('has-error').should.be.false; }); }); }); @@ -2209,22 +2154,13 @@ describe('directive',function(){ } }); + + setTimeout(function() { scope.person.switch = false; scope.$apply(); - scope.person.should.deep.equal({ - "switch": false, - "list": [ - { - "sub": { - }, - } - ], - "deep": { - "sub": { - } - } + "switch": false }); done(); }); @@ -2359,7 +2295,6 @@ describe('directive',function(){ setTimeout(function() { scope.person.switch = false; scope.$apply(); - console.log(JSON.stringify(scope.person,undefined,2)) scope.person.should.deep.equal({ "switch": false, "list": [ diff --git a/test/directives/sf-messages-test.js b/test/directives/sf-messages-test.js index c41b07f62..8a872bc3c 100644 --- a/test/directives/sf-messages-test.js +++ b/test/directives/sf-messages-test.js @@ -1,7 +1,6 @@ chai.should(); describe('directive',function() { - beforeEach(module('templates')); beforeEach(module('schemaForm')); beforeEach( //We don't need no sanitation. We don't need no thought control. diff --git a/test/protractor/conf.js b/test/protractor/conf.js deleted file mode 100644 index 8f51a93ee..000000000 --- a/test/protractor/conf.js +++ /dev/null @@ -1,3 +0,0 @@ -exports.config = { - seleniumAddress: 'http://localhost:4444/wd/hub' -} diff --git a/test/protractor/specs/custom-validation.js b/test/protractor/specs/custom-validation.js deleted file mode 100644 index 98b05bf8b..000000000 --- a/test/protractor/specs/custom-validation.js +++ /dev/null @@ -1,74 +0,0 @@ -describe('Schema Form custom validators', function() { - it('should have a form with content', function() { - browser.get('http://localhost:8080/examples/custom-validators.html'); - - expect(element(by.css('form')).getInnerHtml()).not.toEqual(''); - }); - - describe('#name', function() { - it('should not complain if it gets a normal name', function() { - browser.get('http://localhost:8080/examples/custom-validators.html'); - var input = element.all(by.css('form input')).first(); - input.sendKeys('Joe Schmoe'); - - expect(input.getAttribute('value')).toEqual('Joe Schmoe'); - expect(input.evaluate('ngModel.$valid')).toEqual(true); - - }); - - it('should complain if it gets a "Bob" as a name', function() { - browser.get('http://localhost:8080/examples/custom-validators.html'); - var input = element.all(by.css('form input')).first(); - input.sendKeys('Bob'); - - expect(input.getAttribute('value')).toEqual('Bob'); - expect(input.evaluate('ngModel.$valid')).toEqual(false); - }); - }); - - describe('#email', function() { - it('should not complain if it gets a normal email', function() { - browser.get('http://localhost:8080/examples/custom-validators.html'); - var input = element.all(by.css('form input')).get(1); - input.sendKeys('foo@mailinator.com'); - - expect(input.getAttribute('value')).toEqual('foo@mailinator.com'); - expect(input.evaluate('ngModel.$valid')).toEqual(true); - - }); - - it('should complain if it gets a my email', function() { - browser.get('http://localhost:8080/examples/custom-validators.html'); - var input = element.all(by.css('form input')).get(1); - input.sendKeys('david.lgj@gmail.com'); - - expect(input.getAttribute('value')).toEqual('david.lgj@gmail.com'); - expect(input.evaluate('ngModel.$valid')).toEqual(false); - }); - }); - - describe('#comment', function() { - it('should not complain if it gets a normal email', function() { - browser.get('http://localhost:8080/examples/custom-validators.html'); - var input = element.all(by.css('form input')).get(1); - input.sendKeys('foo@mailinator.com'); - - expect(input.getAttribute('value')).toEqual('foo@mailinator.com'); - expect(input.evaluate('ngModel.$valid')).toEqual(true); - - }); - - it('should complain if it gets a my email', function() { - browser.get('http://localhost:8080/examples/custom-validators.html'); - var input = element.all(by.css('form input')).get(1); - input.sendKeys('david.lgj@gmail.com'); - - expect(input.getAttribute('value')).toEqual('david.lgj@gmail.com'); - expect(input.evaluate('ngModel.$valid')).toEqual(false); - }); - }); - - - - -}); diff --git a/test/protractor/specs/validation-messages.js b/test/protractor/specs/validation-messages.js deleted file mode 100644 index 323275649..000000000 --- a/test/protractor/specs/validation-messages.js +++ /dev/null @@ -1,66 +0,0 @@ -/* global browser, it, describe, element, by */ - -describe('Schema Form validation messages', function() { - - describe('#string', function() { - var URL = 'http://localhost:8080/examples/bootstrap-example.html#/86fb7505a8ab6a43bc70'; - - it('should not complain if it gets a normal string', function() { - browser.get(URL); - var input = element.all(by.css('form[name=ngform] input')).first(); - input.sendKeys('string'); - - expect(input.getAttribute('value')).toEqual('string'); - expect(input.evaluate('ngModel.$valid')).toEqual(true); - - }); - - - var validationMessageTestBuider = function(nr, value, validationMessage) { - it('should say "' + validationMessage + '" when input is ' + value, function() { - browser.get(URL); - var input = element.all(by.css('form[name=ngform] input')).get(nr); - input.sendKeys(value); - - var message = element.all(by.css('form[name=ngform] div[sf-message]')).get(nr); - expect(input.evaluate('ngModel.$valid')).toEqual(false); - expect(message.getText()).toEqual(validationMessage); - - }); - }; - - var stringTests = { - 's': 'String is too short (1 chars), minimum 3', - 'tooo long string': 'String is too long (11 chars), maximum 10', - 'foo 66': 'String does not match pattern: ^[a-zA-Z ]+$' - }; - - Object.keys(stringTests).forEach(function(value) { - validationMessageTestBuider(0, value, stringTests[value]); - }); - - - var integerTests = { - '3': '3 is less than the allowed minimum of 6', - '66': '66 is greater than the allowed maximum of 50', - '11': 'Value is not a multiple of 3', - 'aaa': 'Value is not a valid number' - }; - - Object.keys(integerTests).forEach(function(value) { - validationMessageTestBuider(1, value, integerTests[value]); - }); - - - it('should say "Required" when fields are required', function() { - browser.get(URL); - element.all(by.css('form[name=ngform]')).submit(); - var input = element.all(by.css('form[name=ngform] input')).get(1); - - var message = element.all(by.css('form[name=ngform] div[sf-message]')).get(1); - expect(input.evaluate('ngModel.$valid')).toEqual(false); - expect(message.getText()).toEqual('Required'); - - }); - }); -}); From 7ebab55555debe8950ca824382eb2e33185ee44d Mon Sep 17 00:00:00 2001 From: David Jensen Date: Thu, 20 Aug 2015 16:39:36 +0200 Subject: [PATCH 07/23] Fixed template type tests --- test/directives/schema-form-test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/directives/schema-form-test.js b/test/directives/schema-form-test.js index 6ba413aa0..6e53b8685 100644 --- a/test/directives/schema-form-test.js +++ b/test/directives/schema-form-test.js @@ -1768,7 +1768,7 @@ describe('directive',function(){ }); }); - it.skip('should use supplied template with template field type',function() { + it('should use supplied template with template field type',function() { inject(function($compile, $rootScope){ var scope = $rootScope.$new(); @@ -1793,12 +1793,12 @@ describe('directive',function(){ $compile(tmpl)(scope); $rootScope.$apply(); - tmpl.children().eq(0).html().should.be.eq('
Hello World
') + tmpl.html().should.be.eq('
Hello World
') }); }); - it.skip('should load template by templateUrl, with template field type',function() { + it('should load template by templateUrl, with template field type',function() { inject(function($compile, $rootScope, $httpBackend){ @@ -1831,7 +1831,7 @@ describe('directive',function(){ $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); - tmpl.children().eq(0).html().should.be.eq('
Hello World
') + tmpl.html().should.be.eq('
Hello World
'); }); }); From 2a9460bc4ae1886b475b6152732a28e87e2cb9c8 Mon Sep 17 00:00:00 2001 From: Chris Cornwell Date: Wed, 26 Aug 2015 12:43:07 -0700 Subject: [PATCH 08/23] update docs to include sortOptions key on array form objects --- docs/index.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index a91f601c5..ff55213f4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1231,8 +1231,9 @@ could be changed using attribute `add`, see example below. If you like to have drag and drop reordering of arrays you also need [ui-sortable](https://github.com/angular-ui/ui-sortable) and its dependencies [jQueryUI](http://jqueryui.com/), see *ui-sortable* documentation for details of -what parts of jQueryUI that is needed. You can safely ignore these if you don't -need the reordering. +what parts of jQueryUI that is needed. You can also pass options to the *ui-sortable* directive +by including a `sortOptions` key on the form object. Check the *ui-sortable* documentation +for a complete list of available options. You can safely ignore these if you don't need the reordering. In the form definition you can refer to properties of an array item by the empty bracket notation. In the `key` simply end the name of the array with `[]` From f6f6a5519a7b797fb48d006c0b49c500df56d16f Mon Sep 17 00:00:00 2001 From: Edward Knowles Date: Mon, 14 Sep 2015 16:22:35 +0100 Subject: [PATCH 09/23] fix rouge comma in example --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index a91f601c5..68f33fc92 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1030,7 +1030,7 @@ function FormCtrl($scope) { type: "radiobuttons", titleMap: [ { value: "one", name: "One" }, - { value, "two", name: "More..." } + { value: "two", name: "More..." } ] } ]; From d44d28bd561e69b9ebd1c0027a4f3c4d5242a24e Mon Sep 17 00:00:00 2001 From: David Jensen Date: Fri, 18 Sep 2015 10:27:54 +0200 Subject: [PATCH 10/23] Updated extending docs Not finished yet, but it's a start --- docs/extending.md | 197 +++++++++++++++++++++++++++++++++++++++++++++- docs/index.md | 2 +- 2 files changed, 195 insertions(+), 4 deletions(-) diff --git a/docs/extending.md b/docs/extending.md index c4e5fd035..169d25f23 100644 --- a/docs/extending.md +++ b/docs/extending.md @@ -1,7 +1,198 @@ Extending Schema Form ===================== -Extending schema form with new form field via add-ons or changing to another CSS framework (a new -decorator) is easy. But how you do it has changed recently due to "the new builder". +Schema Form is designed to be easily extended. You can add your own custom fields or completely +change the how the entire form is rendered. -New documentation is in the works and will come soon. +A custom field is called an **add-on** and you can find community add-ons listed over at +[schemaform.io](http://schemaform.io). + +To completely change how the entire field is rendered you need to create what we call a **decorator**. +A decorator is actually a collection of add-ons that at least cover the basic field types +that a schema can default to, but usually a lot more. + +But before we get into the details of how you define a decorator or an add-on, let's take a look at how schema form builds forms. + +How the form is built +---------------------- +Schema Form uses the [sfBuilder](https://github.com/Textalk/angular-schema-form/blob/development/src/services/builder.js) +service to recursively build the DOM elements of the form from a *canonical form definition*, that +is our fancy word for an internal representation of a merge between the schema and the form. + +It's always an array of object and each object at least have the property `type`. If a `type` was +not set in the form definition given to `sf-form` the schema is used to get a default. + +Example canonical form def. +```js +[ + { + type: 'text' + key: 'name' + }, + { + type: 'fieldset', + item: [ + { + type: 'textarea', + key: 'comment' + } + ] + } +] +``` + +#### The actual building +So to build a form from a canonical form definition as in the example above the builder service loops +over and for each type asks the decorator for a template, it adds it to a document fragment. + +After adding the template it also asks the decorator if that type has a *builder* +function (actually it's usually a list of functions). If so it calls it with the DOM of its template, +the form definition for that field and other useful stuff. This way the builder can modify and +prepare the template depending on options in that fields form object. + +Nested fields, as with the fieldset above in the example above, builds it's children with such a +*builder* function. + +This all happens in one large swoop and the finished document fragment is popped inside the form +and then `$compile` is used to kick start it's directives. + + +Creating an add-on +------------------ + +So to create an add-on you need two things, a template and some *builder functions*. Fortunately +schema form got you covered with a couple of standard builders so most of the time you will only +need a template. + +To register your template to be used when the form definition has a specific type you use the +`schemaFormDecoratorsProvider.defineAddOn`. + +Ex. +```js +angular.module('myAddOnModule', ['schemaForm']).config(function(schemaFormDecoratorsProvider, sfBuilderProvider) { + + schemaFormProvider.defineAddOn( + 'bootstrapDecorator', // Name of the decorator you want to add to. + 'awesome', // Form type that should render this add-on + 'templates/my/addon.html', // Template name in $templateCache + sfBuilderProvider.stdBuilders // List of builder functions to apply. + ); + +}); +``` + +The standards builders are `[sfField, ngModel, ngModelOptions, condition]`, see usage details below. + +#### The Template +So whats in a template? You usually need a couple of things: + + 1. Usually a top level element that surrounds your template is a good idea. The `sfField` builder + slaps on a `sfField` directive that exposes the current form object on scope as `form`. + 1. A `sf-field-model` somewhere so that the `ngModel` builder can add a proper `ngModel` to bind + your model value to. + 1. A `schema-validate="form"` directive on the same element to enable schema validation. + 1. A `
` to display description or error messages. + +Basic template example: +```html +
+
+ +
+ +``` + +**BIG FAT CAVEAT** +Ok, so currently there is something really ugly here. The bootstrap (and material) decorator uses +a build step (gulp-angular-templatecache) to "compile" the template into a javascript file that +basically chucks the template into `$templateCache`. Currently schema form does *not* support +loading the templates any other way. They need to be in `$templateCache` when rendering. + +This is really ugly and will be fixed. But you have been warned! + + +Defining a decorator +-------------------- +Defining a decorator is basically the same as defining a lot of add-ons. As with add-ons you use +the `schemaFormDecoratorsProvider` again. This time its +`schemaFormDecoratorsProvider.defineDecorator`. + +Ex. +```js +angular.module('myDecoratorModule', ['schemaForm']).config(function(schemaFormDecoratorsProvider, sfBuilderProvider) { + + schemaFormDecoratorsProvider.defineDecorator('awesomeDecorator', { + textarea: {template: base + 'textarea.html', builder: sfBuilderProvider.stdBuilders}, + button: {template: base + 'submit.html', builder: sfBuilderProvider.stdBuilders}, + text: {template: base + 'text.html', builder: sfBuilderProvider.stdBuilders}, + + // The default is special, if the builder can't find a match it uses the default template. + 'default': {template: base + 'default.html', builder: sfBuilderProvider.stdBuilders} + }, []); +}); +``` + +### Setting up schema defaults +So you got this shiny new add-on or decorator that adds a fancy field type, but feel a bit bummed out that you +need to specify it in the form definition all the time? Fear not because you can also add a "rule" +to map certain types and conditions in the schema to default to your type. + +You do this by adding to the `schemaFormProvider.defaults` object. The `schemaFormProvider.defaults` +is an object with a key for each type *in JSON Schema* with a array of functions as its value. + +```javscript +var defaults = { + string: [], + object: [], + number: [], + integer: [], + boolean: [], + array: [] +}; +``` + +When schema form traverses the JSON Schema to create default form definitions it first checks the +*JSON Schema type* and then calls on each function in the corresponding list *in order* until a +function actually returns something. That is then used as a defualt. + +This is the function that makes it a datepicker if its a string and has format "date" or "date-time": + +```javascript +var datepicker = function(name, schema, options) { + if (schema.type === 'string' && (schema.format === 'date' || schema.format === 'date-time')) { + var f = schemaFormProvider.stdFormObj(name, schema, options); + f.key = options.path; + f.type = 'datepicker'; + options.lookup[sfPathProvider.stringify(options.path)] = f; + return f; + } +}; + +// Put it first in the list of functions +schemaFormProvider.defaults.string.unshift(datepicker); +``` + +### Sharing your add-on with the world +So you made an add-on, why not share it with us? On the front page, +[http://textalk.github.io/angular-schema-form/](http://textalk.github.io/angular-schema-form/#third-party-addons), we +maintain a list of add ons based on a query of the bower register, and we love to see your add-on +there. + +Any [bower](http://bower.io) package with a name starting with `angular-schema-form-` or that has +the `keyword` `angular-schema-form-add-on` in its `bower.json` will be picked up. It's cached so +there can be a delay of a day or so. + +So [make a bower package](http://bower.io/docs/creating-packages/), add the keyword +`angular-schema-form-add-on` and [register it](http://bower.io/docs/creating-packages/#register)! + + + + +The builders +------------ +TODO: API docs for each builder + + +Useful directives +----------------- +TODO: more in depth about schema-validate and sf-messages diff --git a/docs/index.md b/docs/index.md index c73726a2d..c209b6802 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,7 +5,7 @@ IMPORTANT **Angular Schema Form is undergoing a refactoring and the "bootstrap decorator", i.e. the part with all the HTML has been moved to [github.com/Textalk/angular-schema-form-bootstrap](https://github.com/Textalk/angular-schema-form-bootstrap).** -The documentation below, especially form options is therefore somwhat bootstrap decorator +The documentation below, especially form options is therefore somewhat bootstrap decorator specific. The docs is undergoing updating. From be8445fe5e1f76b2af99d5f2096ed414e264c458 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Fri, 18 Sep 2015 12:12:18 +0200 Subject: [PATCH 11/23] extending docs example error --- docs/extending.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/extending.md b/docs/extending.md index 169d25f23..ee448422e 100644 --- a/docs/extending.md +++ b/docs/extending.md @@ -71,7 +71,7 @@ Ex. ```js angular.module('myAddOnModule', ['schemaForm']).config(function(schemaFormDecoratorsProvider, sfBuilderProvider) { - schemaFormProvider.defineAddOn( + schemaFormDecoratorsProvider.defineAddOn( 'bootstrapDecorator', // Name of the decorator you want to add to. 'awesome', // Form type that should render this add-on 'templates/my/addon.html', // Template name in $templateCache From 9b9680065c91dae4856d18dd746b10dd445d7993 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Wed, 23 Sep 2015 13:23:47 +0200 Subject: [PATCH 12/23] More docs on extending: builders! --- docs/extending.md | 126 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 2 deletions(-) diff --git a/docs/extending.md b/docs/extending.md index ee448422e..176a42e65 100644 --- a/docs/extending.md +++ b/docs/extending.md @@ -190,9 +190,131 @@ So [make a bower package](http://bower.io/docs/creating-packages/), add the keyw The builders ------------ -TODO: API docs for each builder +A collection of useful builders that cover most cases are in the `sfBuilder` service and is accessable +both from the provider and the service on the property `builders`. There is also a list of "standard" +builders, when in doubt use those. + +```js +angular.module('myMod').config(function(sfBuildersProvider) { + + // Standard builders + sfBuildersProvider.stdBuilders; + + // All builders + sfBuildersProvider.builder.sfField; + sfBuildersProvider.builder.condition; + sfBuildersProvider.builder.ngModel; + sfBuildersProvider.builder.ngModelOptions; + sfBuildersProvider.builder.simpleTransclusion; + sfBuildersProvider.builder.transclusion; + sfBuildersProvider.builder.array; + +}); +``` + +Currently the standard builders are: +```js +var stdBuilders = [ + builders.sfField, + builders.ngModel, + builders.ngModelOptions, + builders.condition +]; +``` + + +### builders.sfField +The `sfField` builder adds the `sf-field="..."` directive to *the first child element* in the template, +giving it a correct value. The value is an id number that identifies that specific form object. + +The `sf-field` directive exports the form definition object as `form` on scope and as a lot of useful functions. + +As a rule of thumb you always want this builder. + +### builders.condition +The `condition` builder checks the form definition for the option `condition`. If it's present it adds a +`ng-if` to all top level elements in the template. + +You usually want this as well. + +### builder.ngModel +The `ngModel` builder is maybe the most important builder. It makes sure you get a proper binding to +your model value. + +The `ngModel` builder queries the DOM of the template for all elements that have the attribute `sf-field-model`. Your template may have several of them. `sf-field-model` is *not* a directive, +but depending on it's value the `ngModel` builder will take three different actions. + + +#### sf-field-model +Just `sf-field-model` or `sf-field-model=""` tells the builder to add a `ng-model` directive to this element. +This is a common use case. + +Ex: +DOM before `ngModel` builder: +```html +
+ +
+``` +DOM after `ngModel` builder: +```html +
+ +
+``` + +#### sf-field-model="" +Given a value the `ngModel` builder will treat that value as a *attribute name* and instead of slapping +on a `ng-model` set the specified attributes value. It sets it to the same value as the `ng-model` would have gotten. + +Ex: +DOM before `ngModel` builder: +```html +
+ +
+``` +DOM after `ngModel` builder: +```html +
+ +
+``` + +#### sf-field-model="replaceAll" +With the special value *replaceAll* the `ngModel` builder will instead loop over every attribute on the +element and do a string replacement of `"$$value$$"` with the proper model value. + +Ex: +DOM before `ngModel` builder: +```html +
+ +
+``` +DOM after `ngModel` builder: +```html +
+ +
+``` + +### builders.ngModelOptions +If the form definition has a `ngModelOptions` option specified this builder will slap on a `ng-model-options` +attribute to *the first child element* in the template. + + +### builder.simpleTransclusion +The `simpleTransclusion` builder will recurse and build form items, useful for fieldsets etc. This builder +is simple because it only appends children to the first child element and only checks `form.items`. Useful directives ----------------- -TODO: more in depth about schema-validate and sf-messages +TODO: more in depth about schema-validate, sf-messages and sf-field From 21ecfcb332a0fccec0e89c3721b0773db3461612 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Thu, 24 Sep 2015 21:10:16 +0200 Subject: [PATCH 13/23] Markdown fixes in CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f03d09824..3c65cf6d8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,7 @@ Please avoid including anything from the `dist/` directory as that can make merg always generate these files when we make a new release. ** We're currently in transitioning where a large part of the code, i.e. the bootstrap decorator, -has been moved to it's own repo. It's here [github.com/Textalk/angular-schema-form-bootstrap](https://github.com/Textalk/angular-schema-form-bootstrap)* +has been moved to it's own repo. It's here [github.com/Textalk/angular-schema-form-bootstrap](https://github.com/Textalk/angular-schema-form-bootstrap)** Feel free to submit issues on the main repo anyway though. From eec73a3c704cf0d74adfa360e9068812de3f8504 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Thu, 24 Sep 2015 21:12:44 +0200 Subject: [PATCH 14/23] Ooops. OK now the markdown is fixed. --- CONTRIBUTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3c65cf6d8..03e9192c0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,8 +11,7 @@ request heck of a lot easier for us. Please avoid including anything from the `dist/` directory as that can make merging harder, and we always generate these files when we make a new release. -** We're currently in transitioning where a large part of the code, i.e. the bootstrap decorator, -has been moved to it's own repo. It's here [github.com/Textalk/angular-schema-form-bootstrap](https://github.com/Textalk/angular-schema-form-bootstrap)** +**We're currently in transitioning where a large part of the code, i.e. the bootstrap decorator, has been moved to it's own repo. It's here [github.com/Textalk/angular-schema-form-bootstrap](https://github.com/Textalk/angular-schema-form-bootstrap)** Feel free to submit issues on the main repo anyway though. From e35cddedcc4e4ac5ae2a5cd74b05bdf0aa794204 Mon Sep 17 00:00:00 2001 From: Simon Korn Date: Sat, 17 Oct 2015 11:24:53 +0200 Subject: [PATCH 15/23] Update extending.md --- docs/extending.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/extending.md b/docs/extending.md index 176a42e65..4361488e1 100644 --- a/docs/extending.md +++ b/docs/extending.md @@ -201,13 +201,13 @@ angular.module('myMod').config(function(sfBuildersProvider) { sfBuildersProvider.stdBuilders; // All builders - sfBuildersProvider.builder.sfField; - sfBuildersProvider.builder.condition; - sfBuildersProvider.builder.ngModel; - sfBuildersProvider.builder.ngModelOptions; - sfBuildersProvider.builder.simpleTransclusion; - sfBuildersProvider.builder.transclusion; - sfBuildersProvider.builder.array; + sfBuildersProvider.builders.sfField; + sfBuildersProvider.builders.condition; + sfBuildersProvider.builders.ngModel; + sfBuildersProvider.builders.ngModelOptions; + sfBuildersProvider.builders.simpleTransclusion; + sfBuildersProvider.builders.transclusion; + sfBuildersProvider.builders.array; }); ``` From 30975ea07e41d3e54472cf52bf83f94409c607c6 Mon Sep 17 00:00:00 2001 From: Erich Date: Mon, 26 Oct 2015 12:09:33 +1000 Subject: [PATCH 16/23] introduce main ideas in opening of doco as a newbie, this would have help kick start me! --- docs/index.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/index.md b/docs/index.md index e32f12fa9..3378db86f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -54,8 +54,13 @@ Documentation Basic Usage ----------- -First, expose your schema, form, and model to the $scope. -Don't forget to load the `schemaForm` module. +After installing, load the `schemaForm` module in your module definition. + +Then, in your controller, expose your [schema](http://json-schema.org/), +form, and [model](https://docs.angularjs.org/guide/databinding) to the $scope. +Your schema defines your data structure, the form definition +draws on this definition to define the user interface, and the +model binds the user input to the controller. ```javascript angular.module('myModule', ['schemaForm']) @@ -83,7 +88,8 @@ angular.module('myModule', ['schemaForm']) } ``` -Then load them into Schema Form using the `sfSchema`, `sfForm`, and `sfModel` directives. +Then, in your template, load them into Schema Form using the +`sfSchema`, `sfForm`, and `sfModel` directives. ```html
From 3ca58bfe8610784fcaa92e4187b88b12c2bbb617 Mon Sep 17 00:00:00 2001 From: Timo Stollenwerk Date: Thu, 12 Nov 2015 11:10:32 +0100 Subject: [PATCH 17/23] Add missing ',' to actions examples. This breaks if people copy-paste the example code. --- docs/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/index.md b/docs/index.md index 9c5093ff5..fdeb3a421 100644 --- a/docs/index.md +++ b/docs/index.md @@ -941,7 +941,7 @@ element to the select. { type: "actions", items: [ - { type: 'submit', title: 'Ok' } + { type: 'submit', title: 'Ok' }, { type: 'button', title: 'Cancel', onClick: "cancel()" } ] } @@ -953,7 +953,7 @@ We can change this with ```style``` attribute: { type: "actions", items: [ - { type: 'submit', style: 'btn-success', title: 'Ok' } + { type: 'submit', style: 'btn-success', title: 'Ok' }, { type: 'button', style: 'btn-info', title: 'Cancel', onClick: "cancel()" } ] } @@ -967,7 +967,7 @@ the ```sf-schema``` directive. ```javascript [ - { type: 'submit', title: 'Ok', onClick: function(){ ... } } + { type: 'submit', title: 'Ok', onClick: function(){ ... } }, { type: 'button', title: 'Cancel', onClick: "cancel()" } [ ``` @@ -976,7 +976,7 @@ The submit and other buttons have btn-default as default. We can change this with ```style``` attribute: ```javascript [ - { type: 'submit', style: 'btn-warning', title: 'Ok', onClick: function(){ ... } } + { type: 'submit', style: 'btn-warning', title: 'Ok', onClick: function(){ ... } }, { type: 'button', style: 'btn-danger', title: 'Cancel', onClick: "cancel()" } [ ``` From 3e9681cc4205863e62a79fa3d77ecddc8e2ef0a6 Mon Sep 17 00:00:00 2001 From: Saulius Cepauskas Date: Fri, 19 Feb 2016 19:08:57 +0200 Subject: [PATCH 18/23] Revalidate model after successful custom validation event broadcasted --- src/directives/field.js | 3 +++ src/services/decorators.js | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/directives/field.js b/src/directives/field.js index 556f30c59..66147d3f2 100644 --- a/src/directives/field.js +++ b/src/directives/field.js @@ -188,6 +188,9 @@ angular.module('schemaForm').directive('sfField', scope.ngModel.$setValidity(error, validity === true); if (validity === true) { + // Re-trigger model validator, that model itself would be re-validated + scope.ngModel.$validate(); + // Setting or removing a validity can change the field to believe its valid // but its not. So lets trigger its validation as well. scope.$broadcast('schemaFormValidate'); diff --git a/src/services/decorators.js b/src/services/decorators.js index bd13a94fc..1aa0987bd 100644 --- a/src/services/decorators.js +++ b/src/services/decorators.js @@ -256,6 +256,9 @@ angular.module('schemaForm').provider('schemaFormDecorators', scope.ngModel.$setValidity(error, validity === true); if (validity === true) { + // Re-trigger model validator, that model itself would be re-validated + scope.ngModel.$validate(); + // Setting or removing a validity can change the field to believe its valid // but its not. So lets trigger its validation as well. scope.$broadcast('schemaFormValidate'); From 53ad000051e0435ef81c12037efb8e544f4cf3e6 Mon Sep 17 00:00:00 2001 From: Marcel J Bennett Date: Thu, 3 Mar 2016 17:09:07 +1100 Subject: [PATCH 19/23] Update README.md Update documentation in develop branch to explain lack of commits --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fa38ffb09..f8250e977 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ Angular Schema Form [![Build Status](https://img.shields.io/coveralls/jekyll/jekyll.svg?style=flat-square)](https://coveralls.io/r/Textalk/angular-schema-form?branch=development) - +Are you there? +---------------- +We're still here, in fact we're planning our future right now (2016-03) with a group of **eager contributors**! You may have noticed recent changes to the project GitHub location? Well that is so we can start a discussion in Gitter about how to move forward in a more **open** and **inclusive** way so the burden of development is not all down to David and the good folk at **Textalk**. So please get in touch with the **json-schema-form** org if you want to get involved, otherwise, **get ready for some long overdue progress** soon. Generate forms from JSON schemas using AngularJS! From 35bbcfdf79b318cbd9ca15e9b0e85de3c20c5636 Mon Sep 17 00:00:00 2001 From: Satoshi Tanimoto Date: Wed, 2 Mar 2016 22:35:16 -0800 Subject: [PATCH 20/23] Fix for #121 to redraw with proper defaults The problem was that initialForm was used to generate a form on redraw event, but when it was an object type, it was modified in-place by rendering function. So, when the next redraw event happened, initialForm already had the properties defined, and it prevented the default values from being populated into them. --- src/directives/schema-form.js | 2 +- test/directives/schema-form-test.js | 53 +++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/directives/schema-form.js b/src/directives/schema-form.js index 53d473939..1b21885e4 100644 --- a/src/directives/schema-form.js +++ b/src/directives/schema-form.js @@ -170,7 +170,7 @@ angular.module('schemaForm') // part of the form or schema is chnaged without it being a new instance. scope.$on('schemaFormRedraw', function() { var schema = scope.schema; - var form = scope.initialForm || ['*']; + var form = scope.initialForm ? angular.copy(scope.initialForm) : ['*']; if (schema) { render(schema, form); } diff --git a/test/directives/schema-form-test.js b/test/directives/schema-form-test.js index 2d21e5120..a34553c86 100644 --- a/test/directives/schema-form-test.js +++ b/test/directives/schema-form-test.js @@ -1768,6 +1768,59 @@ describe('directive',function(){ }); }); + it('should redraw form with proper defaults on schemaFormRedraw event',function(done) { + + inject(function($compile, $rootScope){ + var scope = $rootScope.$new(); + scope.person = {}; + + scope.schema = { + type: 'object', + properties: { + name: {type: 'string'} + } + }; + + scope.form = [{ + key: 'name', + type: 'text' + }]; + + scope.options = {formDefaults: {}}; + + var tmpl = angular.element('
'); + + $compile(tmpl)(scope); + $rootScope.$apply(); + + expect(tmpl.find('input').attr('disabled')).to.be.undefined; + + var disable, enable; + disable = function () { + // form element should be disabled + scope.options.formDefaults.readonly = true; + scope.$broadcast('schemaFormRedraw'); + $rootScope.$apply(); + expect(tmpl.find('input').attr('disabled')).eq('disabled'); + + // try to re-enable it by modifying global option + setTimeout(enable, 0); + }; + + enable = function () { + // form element should be back to enabled + scope.options.formDefaults.readonly = false; + scope.$broadcast('schemaFormRedraw'); + $rootScope.$apply(); + expect(tmpl.find('input').attr('disabled')).to.be.undefined; + + done(); + } + + setTimeout(disable, 0); + }); + }); + it('should use supplied template with template field type',function() { inject(function($compile, $rootScope){ From 9fa395312adf6b9dcf2127ceb4303a05f751d964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicklas=20B=C3=B6rjesson?= Date: Fri, 1 Apr 2016 14:48:39 +0200 Subject: [PATCH 21/23] Add information on recent developments --- README.md | 66 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f8250e977..0ab94498c 100644 --- a/README.md +++ b/README.md @@ -7,28 +7,80 @@ Angular Schema Form [![Build Status](https://img.shields.io/travis/Textalk/angular-schema-form.svg?style=flat-square)](https://travis-ci.org/Textalk/angular-schema-form) [![Build Status](https://img.shields.io/coveralls/jekyll/jekyll.svg?style=flat-square)](https://coveralls.io/r/Textalk/angular-schema-form?branch=development) +Generate forms from JSON schemas using AngularJS! -Are you there? ----------------- -We're still here, in fact we're planning our future right now (2016-03) with a group of **eager contributors**! You may have noticed recent changes to the project GitHub location? Well that is so we can start a discussion in Gitter about how to move forward in a more **open** and **inclusive** way so the burden of development is not all down to David and the good folk at **Textalk**. So please get in touch with the **json-schema-form** org if you want to get involved, otherwise, **get ready for some long overdue progress** soon. +Recent developments +=================== +First, as there has been a rather intensive period of planning and change for this project, there have been important new developments for the project. +Lets get into those first(the normal front page continues below): +The json-schema-form standard +----------------------------- +A standard, json-schema-form, is being created. + +The reason is that the concept of combining data, JSON Schema and a form definition is something that isn't just usable in a javascript angular web application, but in any framework, on any platform. +Currently, there ports are [angular-schema-form](https://github.com/json-schema-form/angular-schema-form) and [react-schema-form](https://github.com/networknt/react-schema-form), but delphi-schema-form and laravel-schema-form are planned as well. +To make these ports easier to do, and for everything to work in harmony, a common ground has to be established, a [standard](https://github.com/json-schema-form/json-schema-form). + +Organisational +-------------- + +ASF has changed into using a more open governance model. This basically means that ASF is now governed by more people. +Also, an umbrella organisaton, json-schema-form, has been formed. As you can see, this repo is now a part of that organisation, not Textalk. + +Projects +-------- +After a phase of planning, the following list of projects has been decided upon: https://github.com/json-schema-form/json-schema-form/wiki/Current-projects + +Release 1.0 +----------- +The next major release of ASF will be 1.0. + +The goal for that version is to include the breaking changes that is needed for future developents, like *Of and local $refs: + +* Break out the non-framework specific parts of ASF into a vanilla ES6 module +* Remove the built-in bootstrap decorator, and in doing that require that users wanting to use that load that separately. The reason obviously being the material decorator. + +The reason for the core break out is for all javascript-based ports of the json-schema-form concept to be able to share the same central code base. +Work in that direction is being done in the [json-schema-form-core](https://github.com/json-schema-form/json-schema-form-core) repository. + +Schema builder UI +----------------- +There is now a UI for building schemas and forms being developed at [json-schema-builder repository](https://github.com/json-schema-form/json-schema-builder). +Ralphael Owino (main author), has a [demo up already](http://ralphowino.github.io/schema-form-builder/#/builder). + +Schema and form repository +-------------------------- +This is now a [repository with template schemas and forms](https://github.com/json-schema-form/json-schema-form-repository). +So far all the schema.org types has been converted to JSON schema approximations, and also some has been further resolved and had (huge) forms generated. Schema.org is *big*. + +Documentation +------------- +The documentation is evolving, and it is happening mostly on the wiki: +* [The ASF wiki](https://github.com/json-schema-form/angular-schema-form/wiki) +* [The wiki of the json-schema-form organisation](https://github.com/json-schema-form/json-schema-form/wiki) + +New gitter rooms +---------------- +These are just started. +* Discussion on the [general json-schema-form project being carried out](https://gitter.im/json-schema-form/json-schema-form-projects) +* Discussions on the [angular-specific ASF projects](https://gitter.im/json-schema-form/angular-schema-form-projects) -Generate forms from JSON schemas using AngularJS! The Blog / The Web Site / The Twitter / The Movie ------------------------------------------------- +================================================= [medium.com/@SchemaFormIO](https://medium.com/@SchemaFormIO) / [schemaform.io](http://schemaform.io) / [@SchemaFormIO](http://twitter.com/SchemaFormIO) / [Movie](https://www.youtube.com/watch?v=duBFMipRq2o) If you use ASF in your project/company please let us know! We'd love to feature you on the site. Demo Time! ----------- +========== [Try out the example page](http://schemaform.io/examples/bootstrap-example.html). Try editing the schema or form definition and see what comes out! Hint: By pressing the 'Save to gist' button (top left), you can save your example into a shareable link. What is it? ----------- +=========== Schema Form is a set of AngularJS directives (and a couple of services). It can do two things to make life easier: From 197aa47772286ad710ea4920bbea775e53a6a6de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicklas=20B=C3=B6rjesson?= Date: Fri, 1 Apr 2016 20:50:27 +0200 Subject: [PATCH 22/23] Fix grammar, add links --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0ab94498c..0f9466dcf 100644 --- a/README.md +++ b/README.md @@ -25,12 +25,13 @@ To make these ports easier to do, and for everything to work in harmony, a commo Organisational -------------- -ASF has changed into using a more open governance model. This basically means that ASF is now governed by more people. -Also, an umbrella organisaton, json-schema-form, has been formed. As you can see, this repo is now a part of that organisation, not Textalk. +1. ASF has changed into using a more open governance model. This basically means that ASF is now governed by more people. +2. An umbrella organisaton, json-schema-form, has been formed. As you can see, this repo is now a part of that Github organisation, not Textalk. Projects -------- -After a phase of planning, the following list of projects has been decided upon: https://github.com/json-schema-form/json-schema-form/wiki/Current-projects +After a phase of planning, the following list of projects has been decided upon: +https://github.com/json-schema-form/json-schema-form/wiki/Current-projects Release 1.0 ----------- @@ -47,12 +48,13 @@ Work in that direction is being done in the [json-schema-form-core](https://gith Schema builder UI ----------------- There is now a UI for building schemas and forms being developed at [json-schema-builder repository](https://github.com/json-schema-form/json-schema-builder). + Ralphael Owino (main author), has a [demo up already](http://ralphowino.github.io/schema-form-builder/#/builder). Schema and form repository -------------------------- This is now a [repository with template schemas and forms](https://github.com/json-schema-form/json-schema-form-repository). -So far all the schema.org types has been converted to JSON schema approximations, and also some has been further resolved and had (huge) forms generated. Schema.org is *big*. +So far all the [schema.org types](http://schema.org/docs/full.html) has been converted to JSON schema approximations, and also some has been further resolved and had (huge) forms generated. Schema.org is *big*. Documentation ------------- @@ -63,7 +65,7 @@ The documentation is evolving, and it is happening mostly on the wiki: New gitter rooms ---------------- These are just started. -* Discussion on the [general json-schema-form project being carried out](https://gitter.im/json-schema-form/json-schema-form-projects) +* Discussions on the [general json-schema-form projects being carried out](https://gitter.im/json-schema-form/json-schema-form-projects) * Discussions on the [angular-specific ASF projects](https://gitter.im/json-schema-form/angular-schema-form-projects) From 70d55b65cf0a86b58e9c1da5ddad4a33e48e5387 Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 4 Apr 2016 00:09:06 +1000 Subject: [PATCH 23/23] Release 0.8.13 Maintenance release 0.8.13 Bugfix copy the initial form default on form redraw @stanimoto Bugfix for revalidating the model after custom validation event is broadcast @cepauskas New event value. The form name is now passed in validate and error events @tomsowerby & @LeonardoGentile --- CHANGELOG | 30 ++++--- dist/schema-form.js | 170 ++++++++++++++++++++++------------------ dist/schema-form.min.js | 2 +- gulp/tasks/jscs.js | 4 +- gulp/tasks/minify.js | 48 ++++++------ package.json | 11 +-- 6 files changed, 143 insertions(+), 122 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 66bceb2da..956a31f6a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +v0.8.13 +------- + * Bugfix copy the initial form default on form redraw @stanimoto + * Bugfix for revalidating the model after custom validation event is broadcast @cepauskas + * New event value. The form name is now passed in validate and error events @tomsowerby & @LeonardoGentile + v0.8.12 ------- * Bugfix for `condition` builder. It had a typo that broke it. @@ -118,14 +124,14 @@ v0.7.11 v0.7.10 ------ -* Accessability additions, thanks @stramel -* Updates to the gulp tasks, thanks @stramel -* Updated to bower, thanks @Dervisevic -* Updates to documentation & README & Contributing.md, thanks @davidlgj @Dervisevic -* Speed optimization, thanks @stramel -* Call ngModel.$setDirty() when deleting from array, thanks @qstrahl -* Don't override readonly if specified on items individually, thanks @qstrahl -* Update to the examples, thanks @mike-marcacci + * Accessability additions, thanks @stramel + * Updates to the gulp tasks, thanks @stramel + * Updated to bower, thanks @Dervisevic + * Updates to documentation & README & Contributing.md, thanks @davidlgj @Dervisevic + * Speed optimization, thanks @stramel + * Call ngModel.$setDirty() when deleting from array, thanks @qstrahl + * Don't override readonly if specified on items individually, thanks @qstrahl + * Update to the examples, thanks @mike-marcacci v0.7.9 ------ @@ -264,7 +270,7 @@ We're celebrating actual useful functionality by bumping minor version, yay! v0.0.4 ------ -* Fieldsets now properly merge schema defaults. -* Directives for "manual" decorator usage. -* Basic support for buttons. -* Basic support for custom validation error messages. + * Fieldsets now properly merge schema defaults. + * Directives for "manual" decorator usage. + * Basic support for buttons. + * Basic support for custom validation error messages. diff --git a/dist/schema-form.js b/dist/schema-form.js index 7b5b29bde..ff784fa7d 100644 --- a/dist/schema-form.js +++ b/dist/schema-form.js @@ -65,81 +65,6 @@ angular.module('schemaForm').provider('sfPath', }; }]); -/** - * @ngdoc service - * @name sfSelect - * @kind function - * - */ -angular.module('schemaForm').factory('sfSelect', ['sfPath', function(sfPath) { - var numRe = /^\d+$/; - - /** - * @description - * Utility method to access deep properties without - * throwing errors when things are not defined. - * Can also set a value in a deep structure, creating objects when missing - * ex. - * var foo = Select('address.contact.name',obj) - * Select('address.contact.name',obj,'Leeroy') - * - * @param {string} projection A dot path to the property you want to get/set - * @param {object} obj (optional) The object to project on, defaults to 'this' - * @param {Any} valueToSet (opional) The value to set, if parts of the path of - * the projection is missing empty objects will be created. - * @returns {Any|undefined} returns the value at the end of the projection path - * or undefined if there is none. - */ - return function(projection, obj, valueToSet) { - if (!obj) { - obj = this; - } - //Support [] array syntax - var parts = typeof projection === 'string' ? sfPath.parse(projection) : projection; - - if (typeof valueToSet !== 'undefined' && parts.length === 1) { - //special case, just setting one variable - obj[parts[0]] = valueToSet; - return obj; - } - - if (typeof valueToSet !== 'undefined' && - typeof obj[parts[0]] === 'undefined') { - // We need to look ahead to check if array is appropriate - obj[parts[0]] = parts.length > 2 && numRe.test(parts[1]) ? [] : {}; - } - - var value = obj[parts[0]]; - for (var i = 1; i < parts.length; i++) { - // Special case: We allow JSON Form syntax for arrays using empty brackets - // These will of course not work here so we exit if they are found. - if (parts[i] === '') { - return undefined; - } - if (typeof valueToSet !== 'undefined') { - if (i === parts.length - 1) { - //last step. Let's set the value - value[parts[i]] = valueToSet; - return valueToSet; - } else { - // Make sure to create new objects on the way if they are not there. - // We need to look ahead to check if array is appropriate - var tmp = value[parts[i]]; - if (typeof tmp === 'undefined' || tmp === null) { - tmp = numRe.test(parts[i + 1]) ? [] : {}; - value[parts[i]] = tmp; - } - value = tmp; - } - } else if (value) { - //Just get nex value. - value = value[parts[i]]; - } - } - return value; - }; -}]); - // FIXME: type template (using custom builder) angular.module('schemaForm').provider('sfBuilder', ['sfPathProvider', function(sfPathProvider) { @@ -682,6 +607,9 @@ angular.module('schemaForm').provider('schemaFormDecorators', scope.ngModel.$setValidity(error, validity === true); if (validity === true) { + // Re-trigger model validator, that model itself would be re-validated + scope.ngModel.$validate(); + // Setting or removing a validity can change the field to believe its valid // but its not. So lets trigger its validation as well. scope.$broadcast('schemaFormValidate'); @@ -1553,6 +1481,81 @@ angular.module('schemaForm').provider('schemaForm', }]); +/** + * @ngdoc service + * @name sfSelect + * @kind function + * + */ +angular.module('schemaForm').factory('sfSelect', ['sfPath', function(sfPath) { + var numRe = /^\d+$/; + + /** + * @description + * Utility method to access deep properties without + * throwing errors when things are not defined. + * Can also set a value in a deep structure, creating objects when missing + * ex. + * var foo = Select('address.contact.name',obj) + * Select('address.contact.name',obj,'Leeroy') + * + * @param {string} projection A dot path to the property you want to get/set + * @param {object} obj (optional) The object to project on, defaults to 'this' + * @param {Any} valueToSet (opional) The value to set, if parts of the path of + * the projection is missing empty objects will be created. + * @returns {Any|undefined} returns the value at the end of the projection path + * or undefined if there is none. + */ + return function(projection, obj, valueToSet) { + if (!obj) { + obj = this; + } + //Support [] array syntax + var parts = typeof projection === 'string' ? sfPath.parse(projection) : projection; + + if (typeof valueToSet !== 'undefined' && parts.length === 1) { + //special case, just setting one variable + obj[parts[0]] = valueToSet; + return obj; + } + + if (typeof valueToSet !== 'undefined' && + typeof obj[parts[0]] === 'undefined') { + // We need to look ahead to check if array is appropriate + obj[parts[0]] = parts.length > 2 && numRe.test(parts[1]) ? [] : {}; + } + + var value = obj[parts[0]]; + for (var i = 1; i < parts.length; i++) { + // Special case: We allow JSON Form syntax for arrays using empty brackets + // These will of course not work here so we exit if they are found. + if (parts[i] === '') { + return undefined; + } + if (typeof valueToSet !== 'undefined') { + if (i === parts.length - 1) { + //last step. Let's set the value + value[parts[i]] = valueToSet; + return valueToSet; + } else { + // Make sure to create new objects on the way if they are not there. + // We need to look ahead to check if array is appropriate + var tmp = value[parts[i]]; + if (typeof tmp === 'undefined' || tmp === null) { + tmp = numRe.test(parts[i + 1]) ? [] : {}; + value[parts[i]] = tmp; + } + value = tmp; + } + } else if (value) { + //Just get nex value. + value = value[parts[i]]; + } + } + return value; + }; +}]); + /* Common code for validating a value against its form and schema definition */ /* global tv4 */ angular.module('schemaForm').factory('sfValidator', [function() { @@ -2109,6 +2112,9 @@ angular.module('schemaForm').directive('sfField', scope.ngModel.$setValidity(error, validity === true); if (validity === true) { + // Re-trigger model validator, that model itself would be re-validated + scope.ngModel.$validate(); + // Setting or removing a validity can change the field to believe its valid // but its not. So lets trigger its validation as well. scope.$broadcast('schemaFormValidate'); @@ -2677,7 +2683,7 @@ angular.module('schemaForm') // part of the form or schema is chnaged without it being a new instance. scope.$on('schemaFormRedraw', function() { var schema = scope.schema; - var form = scope.initialForm || ['*']; + var form = scope.initialForm ? angular.copy(scope.initialForm) : ['*']; if (schema) { render(schema, form); } @@ -2817,7 +2823,13 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', '$parse var schema = form.schema; // A bit ugly but useful. - scope.validateField = function() { + scope.validateField = function(formName) { + + // If we have specified a form name, and this model is not within + // that form, then leave things be. + if(formName != undefined && ngModel.$$parentForm.$name !== formName) { + return; + } // Special case: arrays // TODO: Can this be generalized in a way that works consistently? @@ -2864,7 +2876,9 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', '$parse }); // Listen to an event so we can validate the input on request - scope.$on('schemaFormValidate', scope.validateField); + scope.$on('schemaFormValidate', function(event, formName) { + scope.validateField(formName); + }); scope.schemaError = function() { return error; diff --git a/dist/schema-form.min.js b/dist/schema-form.min.js index 5f7b06c45..ffa52766f 100644 --- a/dist/schema-form.min.js +++ b/dist/schema-form.min.js @@ -1 +1 @@ -!function(e,t){"function"==typeof define&&define.amd?define(["angular","objectpath","tv4"],t):"object"==typeof exports?module.exports=t(require("angular"),require("objectpath"),require("tv4")):e.schemaForm=t(e.angular,e.objectpath,e.tv4)}(this,function(e,t,r){var n=[];try{e.module("ngSanitize"),n.push("ngSanitize")}catch(i){}try{e.module("ui.sortable"),n.push("ui.sortable")}catch(i){}try{e.module("angularSpectrumColorpicker"),n.push("angularSpectrumColorpicker")}catch(i){}var o=e.module("schemaForm",n);return e.module("schemaForm").provider("sfPath",[function(){var r=window.ObjectPath||t,n={parse:r.parse};1===e.version.major&&e.version.minor<3?n.stringify=function(e){return Array.isArray(e)?e.join("."):e.toString()}:n.stringify=r.stringify,n.normalize=function(e,t){return n.stringify(Array.isArray(e)?e:n.parse(e),t)},this.parse=n.parse,this.stringify=n.stringify,this.normalize=n.normalize,this.$get=function(){return n}}]),e.module("schemaForm").factory("sfSelect",["sfPath",function(e){var t=/^\d+$/;return function(r,n,i){n||(n=this);var o="string"==typeof r?e.parse(r):r;if("undefined"!=typeof i&&1===o.length)return n[o[0]]=i,n;"undefined"!=typeof i&&"undefined"==typeof n[o[0]]&&(n[o[0]]=o.length>2&&t.test(o[1])?[]:{});for(var a=n[o[0]],l=1;l0&&e.fieldFrag.firstChild.setAttribute("ng-model-options",JSON.stringify(e.form.ngModelOptions))},transclusion:function(e){var t=e.fieldFrag.querySelectorAll("[sf-field-transclude]");if(t.length)for(var r=0;r0;)m.appendChild(p.childNodes[0]);var v={fieldFrag:m,form:c,lookup:u,state:s,path:a+"["+f+"]",build:function(e,n,i){return l(e,t,r,o,n,i,u)}},y=c.builder||d.builder;"function"==typeof y?y(v):y.forEach(function(e){e(v)}),(i(c,o)||e).appendChild(m)}else{var g=document.createElement(n(t.__name,"-"));s.arrayCompatFlag?g.setAttribute("form","copyWithIndex($index)"):g.setAttribute("form",a+"["+f+"]"),(i(c,o)||e).appendChild(g)}return e},c),c};return{build:function(t,r,n,i){return l(t,r,function(t,r){return"template"===t.type?t.template:e.get(r.template)},n,void 0,void 0,i)},builder:o,stdBuilders:a,internalBuild:l}}]}]),e.module("schemaForm").provider("schemaFormDecorators",["$compileProvider","sfPathProvider",function(t,r){var n="",i={},o=function(e,t){"sfDecorator"===e&&(e=n);var r=i[e];return r[t.type]?r[t.type].template:r["default"].template},a=function(n){t.directive(n,["$parse","$compile","$http","$templateCache","$interpolate","$q","sfErrorMessage","sfPath","sfSelect",function(t,i,a,l,s,u,c,f,d){return{restrict:"AE",replace:!1,transclude:!1,scope:!0,require:"?^sfSchema",link:function(t,m,p,h){t.$on("schemaFormPropagateNgModelController",function(e,r){e.stopPropagation(),e.preventDefault(),t.ngModel=r}),t.showTitle=function(){return t.form&&t.form.notitle!==!0&&t.form.title},t.listToCheckboxValues=function(t){var r={};return e.forEach(t,function(e){r[e]=!0}),r},t.checkboxValuesToList=function(t){var r=[];return e.forEach(t,function(e,t){e&&r.push(t)}),r},t.buttonClick=function(r,n){e.isFunction(n.onClick)?n.onClick(r,n):e.isString(n.onClick)&&(h?h.evalInParentScope(n.onClick,{$event:r,form:n}):t.$eval(n.onClick,{$event:r,form:n}))},t.evalExpr=function(e,r){return h?h.evalInParentScope(e,r):t.$eval(e,r)},t.evalInScope=function(e,r){return e?t.$eval(e,r):void 0},t.interp=function(e,t){return e&&s(e)(t)},t.hasSuccess=function(){return t.ngModel?t.ngModel.$valid&&(!t.ngModel.$pristine||!t.ngModel.$isEmpty(t.ngModel.$modelValue)):!1},t.hasError=function(){return t.ngModel?t.ngModel.$invalid&&!t.ngModel.$pristine:!1},t.errorMessage=function(e){return c.interpolate(e&&e.code+""||"default",t.ngModel&&t.ngModel.$modelValue||"",t.ngModel&&t.ngModel.$viewValue||"",t.form,t.options&&t.options.validationMessage)};var v=t.$watch(p.form,function(s){if(s){s.ngModelOptions=s.ngModelOptions||{},t.form=s;var c;if("template"===s.type&&s.template)c=u.when(s.template);else{var p="template"===s.type?s.templateUrl:o(n,s);c=a.get(p,{cache:l}).then(function(e){return e.data})}c.then(function(n){if(s.key){var o=s.key?r.stringify(s.key).replace(/"/g,"""):"";n=n.replace(/\$\$value\$\$/g,"model"+("["!==o[0]?".":"")+o)}if(m.html(n),s.condition){var a='evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex})';s.key&&(a='evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex, "modelValue": model'+f.stringify(s.key)+"})"),e.forEach(m.children(),function(e){var t=e.getAttribute("ng-if");e.setAttribute("ng-if",t?"("+t+") || ("+a+")":a)})}i(m.contents())(t)}),s.key&&(t.$on("schemaForm.error."+s.key.join("."),function(e,r,n,i){(n===!0||n===!1)&&(i=n,n=void 0),t.ngModel&&r&&(t.ngModel.$setDirty?t.ngModel.$setDirty():(t.ngModel.$dirty=!0,t.ngModel.$pristine=!1),n&&(s.validationMessage||(s.validationMessage={}),s.validationMessage[r]=n),t.ngModel.$setValidity(r,i===!0),i===!0&&t.$broadcast("schemaFormValidate"))}),t.$on("$destroy",function(){if(!t.externalDestructionInProgress){var e=s.destroyStrategy||t.options&&t.options.destroyStrategy||"remove";if(s.key&&"retain"!==e){var r=t.model;if(s.key.length>1&&(r=d(s.key.slice(0,s.key.length-1),r)),void 0===r)return;var n=s.schema&&s.schema.type||"";"empty"===e&&-1!==n.indexOf("string")?r[s.key.slice(-1)]="":"empty"===e&&-1!==n.indexOf("object")?r[s.key.slice(-1)]={}:"empty"===e&&-1!==n.indexOf("array")?r[s.key.slice(-1)]=[]:"null"===e?r[s.key.slice(-1)]=null:delete r[s.key.slice(-1)]}}})),v()}})}}}])},l=function(r,n,i){i=e.isDefined(i)?i:!1,t.directive("sf"+e.uppercase(r[0])+r.substr(1),function(){return{restrict:"EAC",scope:!0,replace:!0,transclude:i,template:'',link:function(t,n,i){var o={items:"c",titleMap:"c",schema:"c"},a={type:r},l=!0;e.forEach(i,function(r,n){if("$"!==n[0]&&0!==n.indexOf("ng")&&"sfField"!==n){var s=function(r){e.isDefined(r)&&r!==a[n]&&(a[n]=r,l&&a.type&&(a.key||e.isUndefined(i.key))&&(t.form=a,l=!1))};"model"===n?t.$watch(r,function(e){e&&t.model!==e&&(t.model=e)}):"c"===o[n]?t.$watchCollection(r,s):i.$observe(n,s)}})}}})};this.createDecorator=function(t,r){i[t]={__name:t},e.forEach(r,function(e,r){i[t][r]={template:e,replace:!1,builder:[]}}),i[n]||(n=t),a(t)},this.defineDecorator=function(t,r){i[t]={__name:t},e.forEach(r,function(r,n){r.builder=r.builder||[],r.replace=e.isDefined(r.replace)?r.replace:!0,i[t][n]=r}),i[n]||(n=t),a(t)},this.createDirective=l,this.createDirectives=function(t){e.forEach(t,function(e,t){l(t,e)})},this.decorator=function(e){return e=e||n,i[e]},this.addMapping=function(e,t,r,n,o){i[e]&&(i[e][t]={template:r,builder:n,replace:!!o})},this.defineAddOn=function(e,t,r,n){i[e]&&(i[e][t]={template:r,builder:n,replace:!0})},this.$get=function(){return{decorator:function(e){return i[e]||i[n]},defaultDecorator:n}},a("sfDecorator")}]),e.module("schemaForm").provider("sfErrorMessage",function(){var t={"default":"Field does not validate",0:"Invalid type, expected {{schema.type}}",1:"No enum match for: {{viewValue}}",10:'Data does not match any schemas from "anyOf"',11:'Data does not match any schemas from "oneOf"',12:'Data is valid against more than one schema from "oneOf"',13:'Data matches schema from "not"',100:"Value is not a multiple of {{schema.multipleOf}}",101:"{{viewValue}} is less than the allowed minimum of {{schema.minimum}}",102:"{{viewValue}} is equal to the exclusive minimum {{schema.minimum}}",103:"{{viewValue}} is greater than the allowed maximum of {{schema.maximum}}",104:"{{viewValue}} is equal to the exclusive maximum {{schema.maximum}}",105:"Value is not a valid number",200:"String is too short ({{viewValue.length}} chars), minimum {{schema.minLength}}",201:"String is too long ({{viewValue.length}} chars), maximum {{schema.maxLength}}",202:"String does not match pattern: {{schema.pattern}}",300:"Too few properties defined, minimum {{schema.minProperties}}",301:"Too many properties defined, maximum {{schema.maxProperties}}",302:"Required",303:"Additional properties not allowed",304:"Dependency failed - key must exist",400:"Array is too short ({{value.length}}), minimum {{schema.minItems}}",401:"Array is too long ({{value.length}}), maximum {{schema.maxItems}}",402:"Array items are not unique",403:"Additional items not allowed",500:"Format validation failed",501:'Keyword failed: "{{title}}"',600:"Circular $refs",1e3:"Unknown property (not in schema)"};t.number=t[105],t.required=t[302],t.min=t[101],t.max=t[103],t.maxlength=t[201],t.minlength=t[200],t.pattern=t[202],this.setDefaultMessages=function(e){t=e},this.getDefaultMessages=function(){return t},this.setDefaultMessage=function(e,r){t[e]=r},this.$get=["$interpolate",function(r){var n={};return n.defaultMessages=t,n.interpolate=function(n,i,o,a,l){l=l||{};var s=a.validationMessage||{};0===n.indexOf("tv4-")&&(n=n.substring(4));var u=s["default"]||l["default"]||"";[s,l,t].some(function(t){return e.isString(t)||e.isFunction(t)?(u=t,!0):t&&t[n]?(u=t[n],!0):void 0});var c={error:n,value:i,viewValue:o,form:a,schema:a.schema,title:a.title||a.schema&&a.schema.title};return e.isFunction(u)?u(c):r(u)(c)},n}]}),e.module("schemaForm").provider("schemaForm",["sfPathProvider",function(t){var r=function(e){if(Array.isArray(e)&&2==e.length){if("null"===e[0])return e[1];if("null"===e[1])return e[0]}return e},n=function(e){var t=[];return e.forEach(function(e){t.push({name:e,value:e})}),t},i=function(t,r){if(!e.isArray(t)){var n=[];return r?e.forEach(r,function(e,r){n.push({name:t[e],value:e})}):e.forEach(t,function(e,t){n.push({name:e,value:t})}),n}return t},o=function(t,n,i){var o=h[r(n.type)];if(o)for(var a,l=0;l1&&(m={type:"section",items:l.items.map(function(t){return t.ngModelOptions=l.ngModelOptions,e.isUndefined(t.readonly)&&(t.readonly=l.readonly),t})})}if(a.copyWithIndex=function(t){if(!c[t]&&m){var n=e.copy(m);n.arrayIndex=t,r.traverseForm(n,o(t)),c[t]=n}return c[t]},a.appendToArray=function(){var n=s.length,i=a.copyWithIndex(n);if(r.traverseForm(i,function(r){if(r.key){var n;e.isDefined(r["default"])&&(n=r["default"]),e.isDefined(r.schema)&&e.isDefined(r.schema["default"])&&(n=r.schema["default"]),e.isDefined(n)&&t(r.key,a.model,n)}}),n===s.length){var o,u=t("schema.items.type",l);"object"===u?o={}:"array"===u&&(o=[]),s.push(o)}return a.validateArray(),s},a.deleteFromArray=function(e){return s.splice(e,1),a.validateArray(),u&&u.$setDirty&&u.$setDirty(),s},l.titleMap||l.startEmpty===!0||0!==s.length||a.appendToArray(),l.titleMap&&l.titleMap.length>0){a.titleMapValues=[];var p=function(e){a.titleMapValues=[],e=e||[],l.titleMap.forEach(function(t){a.titleMapValues.push(-1!==e.indexOf(t.value))})};p(a.modelArray),a.$watchCollection("modelArray",p),a.$watchCollection("titleMapValues",function(e,t){if(e&&e!==t){for(var r=a.modelArray;r.length>0;)r.pop();l.titleMap.forEach(function(t,n){e[n]&&r.push(t.value)}),a.validateArray()}})}if(u){var h;a.validateArray=function(){var e=n.validate(l,a.modelArray.length>0?a.modelArray:void 0);Object.keys(u.$error).filter(function(e){return 0===e.indexOf("tv4-")}).forEach(function(e){u.$setValidity(e,!0)}),e.valid!==!1||!e.error||""!==e.error.dataPath&&e.error.dataPath!=="/"+l.key[l.key.length-1]||(u.$setViewValue(a.modelArray),h=e.error,u.$setValidity("tv4-"+e.error.code,!1))},a.$on("schemaFormValidate",a.validateArray),a.hasSuccess=function(){return a.options&&a.options.pristine&&a.options.pristine.success===!1?u.$valid&&!u.$pristine&&!u.$isEmpty(u.$modelValue):u.$valid&&(!u.$pristine||!u.$isEmpty(u.$modelValue))},a.hasError=function(){return a.options&&a.options.pristine&&a.options.pristine.errors===!1?u.$invalid&&!u.$pristine:u.$invalid},a.schemaError=function(){return h}}f()}})}}}]),e.module("schemaForm").directive("sfChanged",function(){return{require:"ngModel",restrict:"AC",scope:!1,link:function(t,r,n,i){var o=t.$eval(n.sfChanged);o&&o.onChange&&i.$viewChangeListeners.push(function(){e.isFunction(o.onChange)?o.onChange(i.$modelValue,o):t.evalExpr(o.onChange,{modelValue:i.$modelValue,form:o})})}}}),e.module("schemaForm").directive("sfField",["$parse","$compile","$http","$templateCache","$interpolate","$q","sfErrorMessage","sfPath","sfSelect",function(t,r,n,i,o,a,l,s,u){return{restrict:"AE",replace:!1,transclude:!1,scope:!0,require:"^sfSchema",link:{pre:function(e,t,r,n){e.$on("schemaFormPropagateNgModelController",function(t,r){t.stopPropagation(),t.preventDefault(),e.ngModel=r}),e.form=n.lookup["f"+r.sfField]},post:function(t,r,n,i){t.showTitle=function(){return t.form&&t.form.notitle!==!0&&t.form.title},t.listToCheckboxValues=function(t){var r={};return e.forEach(t,function(e){r[e]=!0}),r},t.checkboxValuesToList=function(t){var r=[];return e.forEach(t,function(e,t){e&&r.push(t)}),r},t.buttonClick=function(r,n){e.isFunction(n.onClick)?n.onClick(r,n):e.isString(n.onClick)&&(i?i.evalInParentScope(n.onClick,{$event:r,form:n}):t.$eval(n.onClick,{$event:r,form:n}))},t.evalExpr=function(e,r){return i?i.evalInParentScope(e,r):t.$eval(e,r)},t.evalInScope=function(e,r){return e?t.$eval(e,r):void 0},t.interp=function(e,t){return e&&o(e)(t)},t.hasSuccess=function(){return t.ngModel?t.options&&t.options.pristine&&t.options.pristine.success===!1?t.ngModel.$valid&&!t.ngModel.$pristine&&!t.ngModel.$isEmpty(t.ngModel.$modelValue):t.ngModel.$valid&&(!t.ngModel.$pristine||!t.ngModel.$isEmpty(t.ngModel.$modelValue)):!1},t.hasError=function(){return t.ngModel?t.options&&t.options.pristine&&t.options.pristine.errors===!1?t.ngModel.$invalid&&!t.ngModel.$pristine:t.ngModel.$invalid:!1},t.errorMessage=function(e){return l.interpolate(e&&e.code+""||"default",t.ngModel&&t.ngModel.$modelValue||"",t.ngModel&&t.ngModel.$viewValue||"",t.form,t.options&&t.options.validationMessage)};var a=t.form;a.key&&(t.$on("schemaForm.error."+a.key.join("."),function(e,r,n,i){(n===!0||n===!1)&&(i=n,n=void 0),t.ngModel&&r&&(t.ngModel.$setDirty?t.ngModel.$setDirty():(t.ngModel.$dirty=!0,t.ngModel.$pristine=!1),n&&(a.validationMessage||(a.validationMessage={}),a.validationMessage[r]=n),t.ngModel.$setValidity(r,i===!0),i===!0&&t.$broadcast("schemaFormValidate"))}),t.$on("$destroy",function(){if(!t.externalDestructionInProgress){var e=a.destroyStrategy||t.options&&t.options.destroyStrategy||"remove";if(a.key&&"retain"!==e){var r=t.model;if(a.key.length>1&&(r=u(a.key.slice(0,a.key.length-1),r)),void 0===r)return;var n=a.schema&&a.schema.type||"";"empty"===e&&-1!==n.indexOf("string")?r[a.key.slice(-1)]="":"empty"===e&&-1!==n.indexOf("object")?r[a.key.slice(-1)]={}:"empty"===e&&-1!==n.indexOf("array")?r[a.key.slice(-1)]=[]:"null"===e?r[a.key.slice(-1)]=null:delete r[a.key.slice(-1)]}}}))}}}}]),e.module("schemaForm").directive("sfMessage",["$injector","sfErrorMessage",function(t,r){var n=t.has("$sanitize")?t.get("$sanitize"):function(e){return e};return{scope:!1,restrict:"EA",link:function(t,i,o){var a="";o.sfMessage&&t.$watch(o.sfMessage,function(e){e&&(a=n(e),u(!!t.ngModel))});var l,s=function(e){e!==l&&(i.html(e),l=e)},u=function(n){if(n)if(t.hasError()){var i=[];e.forEach(t.ngModel&&t.ngModel.$error,function(e,t){e&&i.push(t)}),i=i.filter(function(e){return"schemaForm"!==e});var o=i[0];s(o?r.interpolate(o,t.ngModel.$modelValue,t.ngModel.$viewValue,t.form,t.options&&t.options.validationMessage):a)}else s(a);else s(a)};u();var c=t.$watch("ngModel",function(e){e&&(e.$parsers.push(function(e){return u(!0),e}),e.$formatters.push(function(e){return u(!0),e}),c())});t.$watchCollection("ngModel.$error",function(){u(!!t.ngModel)})}}}]),e.module("schemaForm").directive("sfNewArray",["sfSelect","sfPath","schemaForm",function(t,r,n){return{scope:!1,link:function(i,o,a){i.min=0,i.modelArray=i.$eval(a.sfNewArray);var l=function(){i.modelArray=i.$eval(a.sfNewArray),(!(i.ngModel&&i.ngModel.$pristine&&i.firstDigest)||i.options&&i.options.validateOnRender===!0)&&i.validateField&&i.validateField()},s=function(){i.form&&i.form.onChange&&(e.isFunction(i.form.onChange)?i.form.onChange(i.modelArray,i.form):i.evalExpr(i.form.onChange,{modelValue:i.modelArray,form:i.form}))},u=function(){var e=i.modelArray;if(!e){var n=r.parse(a.sfNewArray);e=[],t(n,i,e),i.modelArray=e}return e},c=i.$watch("form",function(e){if(e){if(e.titleMap||e.startEmpty===!0||i.modelArray&&0!==i.modelArray.length||i.appendToArray(),i.form&&i.form.schema&&i.form.schema.uniqueItems===!0?(i.$watch(a.sfNewArray,l,!0),i.$watch([a.sfNewArray,a.sfNewArray+".length"],s)):i.$watchGroup?i.$watchGroup([a.sfNewArray,a.sfNewArray+".length"],function(){l(),s()}):(i.$watch(a.sfNewArray,function(){l(),s()}),i.$watch(a.sfNewArray+".length",function(){l(),s()})),e.titleMap&&e.titleMap.length>0){i.titleMapValues=[];var t=function(t){i.titleMapValues=[],t=t||[],e.titleMap.forEach(function(e){i.titleMapValues.push(-1!==t.indexOf(e.value))})};t(i.modelArray),i.$watchCollection("modelArray",t),i.$watchCollection("titleMapValues",function(t,r){if(t&&t!==r){for(var n=u();n.length>0;)n.pop();e.titleMap.forEach(function(e,r){t[r]&&n.push(e.value)}),i.validateField&&i.validateField()}})}c()}});i.appendToArray=function(){var r,o=u();if(i.form&&i.form.schema&&i.form.schema.items){var a=i.form.schema.items;a.type&&-1!==a.type.indexOf("object")?(r={},i.options&&i.options.setSchemaDefaults===!1||(r=e.isDefined(a["default"])?a["default"]:r,r&&n.traverseSchema(a,function(n,i){e.isDefined(n["default"])&&t(i,r,n["default"])}))):a.type&&-1!==a.type.indexOf("array")?(r=[],i.options&&i.options.setSchemaDefaults===!1||(r=a["default"]||r)):i.options&&i.options.setSchemaDefaults===!1||(r=a["default"]||r)}return o.push(r),o},i.deleteFromArray=function(e){var t=i.modelArray;return t&&t.splice(e,1),t};var f=function(e){return function(t){t.key&&(t.key[t.key.indexOf("")]=e)}},d={};i.copyWithIndex=function(t){var r=i.form;if(!d[t]){var o=r.items[0];if(r.items.length>1&&(o={type:"section",items:r.items.map(function(t){return t.ngModelOptions=r.ngModelOptions,e.isUndefined(t.readonly)&&(t.readonly=r.readonly),t})}),o){var a=e.copy(o);a.arrayIndex=t,n.traverseForm(a,f(t)),d[t]=a}}return d[t]}}}}]),e.module("schemaForm").directive("sfSchema",["$compile","$http","$templateCache","$q","schemaForm","schemaFormDecorators","sfSelect","sfPath","sfBuilder",function(t,r,n,i,o,a,l,s,u){return{scope:{schema:"=sfSchema",initialForm:"=sfForm",model:"=sfModel",options:"=sfOptions"},controller:["$scope",function(e){this.evalInParentScope=function(t,r){return e.$parent.$eval(t,r)};var t=this;e.lookup=function(e){return e&&(t.lookup=e),t.lookup}}],replace:!1,restrict:"A",transclude:!0,require:"?form",link:function(s,c,f,d,m){s.formCtrl=d;var p={};m(s,function(e){if(e.addClass("schema-form-ignore"),c.prepend(e),c[0].querySelectorAll){var t=c[0].querySelectorAll("[ng-model]");if(t)for(var r=0;r0?i.all(a.map(function(e){return r.get(e.templateUrl,{cache:n}).then(function(t){e.template=t.data})})).then(function(){g(e,t,l)}):g(e,t,l)},g=function(r,n,i){h&&(s.externalDestructionInProgress=!0,h.$destroy(),s.externalDestructionInProgress=!1),h=s.$new(),h.schemaForm={form:i,schema:r},c.children(":not(.schema-form-ignore)").remove();for(var d={},m=c[0].querySelectorAll("*[sf-insert-field]"),p=0;p0&&(v.schema=e,v.form=t,y(e,t))}),s.$on("schemaFormRedraw",function(){var e=s.schema,t=s.initialForm||["*"];e&&y(e,t)}),s.$on("$destroy",function(){s.externalDestructionInProgress=!0}),s.evalExpr=function(e,t){return s.$parent.$eval(e,t)}}}}]),e.module("schemaForm").directive("schemaValidate",["sfValidator","$parse","sfSelect",function(t,r,n){return{restrict:"A",scope:!1,priority:500,require:"ngModel",link:function(r,i,o,a){r.$emit("schemaFormPropagateNgModelController",a);var l=null,s=r.$eval(o.schemaValidate);s.copyValueTo&&a.$viewChangeListeners.push(function(){var t=s.copyValueTo;e.forEach(t,function(e){n(e,r.model,a.$modelValue)})});var u=function(e){if(!s)return e;if(r.options&&r.options.tv4Validation===!1)return e;var n=t.validate(s,e);return Object.keys(a.$error).filter(function(e){return 0===e.indexOf("tv4-")}).forEach(function(e){a.$setValidity(e,!0)}),n.valid?e:(a.$setValidity("tv4-"+n.error.code,!1),l=n.error,a.$validators?e:void 0)};"function"==typeof s.ngModel&&s.ngModel(a),["$parsers","$viewChangeListeners","$formatters"].forEach(function(e){s[e]&&a[e]&&s[e].forEach(function(t){a[e].push(t)})}),["$validators","$asyncValidators"].forEach(function(t){s[t]&&a[t]&&e.forEach(s[t],function(e,r){a[t][r]=e})}),a.$parsers.push(u),a.$validators&&(a.$validators.schemaForm=function(){return!Object.keys(a.$error).some(function(e){return"schemaForm"!==e})});var c=s.schema;r.validateField=function(){c&&-1!==c.type.indexOf("array")&&u(a.$modelValue),a.$setDirty?(a.$setDirty(),a.$setViewValue(a.$viewValue),a.$commitViewValue(),s.required&&a.$isEmpty(a.$modelValue)&&a.$setValidity("tv4-302",!1)):a.$setViewValue(a.$viewValue)},a.$formatters.push(function(e){return!a.$pristine||!r.firstDigest||r.options&&r.options.validateOnRender===!0?(u(a.$modelValue),e):e}),r.$on("schemaFormValidate",r.validateField),r.schemaError=function(){return l}}}}]),o}); \ No newline at end of file +!function(e,t){"function"==typeof define&&define.amd?define(["angular","objectpath","tv4"],t):"object"==typeof exports?module.exports=t(require("angular"),require("objectpath"),require("tv4")):e.schemaForm=t(e.angular,e.objectpath,e.tv4)}(this,function(e,t,r){var n=[];try{e.module("ngSanitize"),n.push("ngSanitize")}catch(i){}try{e.module("ui.sortable"),n.push("ui.sortable")}catch(i){}try{e.module("angularSpectrumColorpicker"),n.push("angularSpectrumColorpicker")}catch(i){}var o=e.module("schemaForm",n);return e.module("schemaForm").provider("sfPath",[function(){var r=window.ObjectPath||t,n={parse:r.parse};1===e.version.major&&e.version.minor<3?n.stringify=function(e){return Array.isArray(e)?e.join("."):e.toString()}:n.stringify=r.stringify,n.normalize=function(e,t){return n.stringify(Array.isArray(e)?e:n.parse(e),t)},this.parse=n.parse,this.stringify=n.stringify,this.normalize=n.normalize,this.$get=function(){return n}}]),e.module("schemaForm").provider("sfBuilder",["sfPathProvider",function(t){var r=/[A-Z]/g,n=function(e,t){return t=t||"_",e.replace(r,function(e,r){return(r?t:"")+e.toLowerCase()})},i=0,o={sfField:function(e){e.fieldFrag.firstChild.setAttribute("sf-field",i),e.lookup["f"+i]=e.form,i++},ngModel:function(e){if(e.form.key){var r=e.form.key;e.state.keyRedaction&&(r=r.slice(e.state.keyRedaction));var n;if(e.state.modelValue)n=e.state.modelValue;else{var i=t.stringify(r).replace(/"/g,""");n=e.state.modelName||"model",i&&(n+=("["!==i[0]?".":"")+i)}for(var o=e.fieldFrag.querySelectorAll("[sf-field-model]"),a=0;a0&&e.fieldFrag.firstChild.setAttribute("ng-model-options",JSON.stringify(e.form.ngModelOptions))},transclusion:function(e){var t=e.fieldFrag.querySelectorAll("[sf-field-transclude]");if(t.length)for(var r=0;r0;)m.appendChild(p.childNodes[0]);var v={fieldFrag:m,form:c,lookup:u,state:s,path:a+"["+f+"]",build:function(e,n,i){return l(e,t,r,o,n,i,u)}},y=c.builder||d.builder;"function"==typeof y?y(v):y.forEach(function(e){e(v)}),(i(c,o)||e).appendChild(m)}else{var g=document.createElement(n(t.__name,"-"));s.arrayCompatFlag?g.setAttribute("form","copyWithIndex($index)"):g.setAttribute("form",a+"["+f+"]"),(i(c,o)||e).appendChild(g)}return e},c),c};return{build:function(t,r,n,i){return l(t,r,function(t,r){return"template"===t.type?t.template:e.get(r.template)},n,void 0,void 0,i)},builder:o,stdBuilders:a,internalBuild:l}}]}]),e.module("schemaForm").provider("schemaFormDecorators",["$compileProvider","sfPathProvider",function(t,r){var n="",i={},o=function(e,t){"sfDecorator"===e&&(e=n);var r=i[e];return r[t.type]?r[t.type].template:r["default"].template},a=function(n){t.directive(n,["$parse","$compile","$http","$templateCache","$interpolate","$q","sfErrorMessage","sfPath","sfSelect",function(t,i,a,l,s,u,c,f,d){return{restrict:"AE",replace:!1,transclude:!1,scope:!0,require:"?^sfSchema",link:function(t,m,p,h){t.$on("schemaFormPropagateNgModelController",function(e,r){e.stopPropagation(),e.preventDefault(),t.ngModel=r}),t.showTitle=function(){return t.form&&t.form.notitle!==!0&&t.form.title},t.listToCheckboxValues=function(t){var r={};return e.forEach(t,function(e){r[e]=!0}),r},t.checkboxValuesToList=function(t){var r=[];return e.forEach(t,function(e,t){e&&r.push(t)}),r},t.buttonClick=function(r,n){e.isFunction(n.onClick)?n.onClick(r,n):e.isString(n.onClick)&&(h?h.evalInParentScope(n.onClick,{$event:r,form:n}):t.$eval(n.onClick,{$event:r,form:n}))},t.evalExpr=function(e,r){return h?h.evalInParentScope(e,r):t.$eval(e,r)},t.evalInScope=function(e,r){return e?t.$eval(e,r):void 0},t.interp=function(e,t){return e&&s(e)(t)},t.hasSuccess=function(){return t.ngModel?t.ngModel.$valid&&(!t.ngModel.$pristine||!t.ngModel.$isEmpty(t.ngModel.$modelValue)):!1},t.hasError=function(){return t.ngModel?t.ngModel.$invalid&&!t.ngModel.$pristine:!1},t.errorMessage=function(e){return c.interpolate(e&&e.code+""||"default",t.ngModel&&t.ngModel.$modelValue||"",t.ngModel&&t.ngModel.$viewValue||"",t.form,t.options&&t.options.validationMessage)};var v=t.$watch(p.form,function(s){if(s){s.ngModelOptions=s.ngModelOptions||{},t.form=s;var c;if("template"===s.type&&s.template)c=u.when(s.template);else{var p="template"===s.type?s.templateUrl:o(n,s);c=a.get(p,{cache:l}).then(function(e){return e.data})}c.then(function(n){if(s.key){var o=s.key?r.stringify(s.key).replace(/"/g,"""):"";n=n.replace(/\$\$value\$\$/g,"model"+("["!==o[0]?".":"")+o)}if(m.html(n),s.condition){var a='evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex})';s.key&&(a='evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex, "modelValue": model'+f.stringify(s.key)+"})"),e.forEach(m.children(),function(e){var t=e.getAttribute("ng-if");e.setAttribute("ng-if",t?"("+t+") || ("+a+")":a)})}i(m.contents())(t)}),s.key&&(t.$on("schemaForm.error."+s.key.join("."),function(e,r,n,i){(n===!0||n===!1)&&(i=n,n=void 0),t.ngModel&&r&&(t.ngModel.$setDirty?t.ngModel.$setDirty():(t.ngModel.$dirty=!0,t.ngModel.$pristine=!1),n&&(s.validationMessage||(s.validationMessage={}),s.validationMessage[r]=n),t.ngModel.$setValidity(r,i===!0),i===!0&&(t.ngModel.$validate(),t.$broadcast("schemaFormValidate")))}),t.$on("$destroy",function(){if(!t.externalDestructionInProgress){var e=s.destroyStrategy||t.options&&t.options.destroyStrategy||"remove";if(s.key&&"retain"!==e){var r=t.model;if(s.key.length>1&&(r=d(s.key.slice(0,s.key.length-1),r)),void 0===r)return;var n=s.schema&&s.schema.type||"";"empty"===e&&-1!==n.indexOf("string")?r[s.key.slice(-1)]="":"empty"===e&&-1!==n.indexOf("object")?r[s.key.slice(-1)]={}:"empty"===e&&-1!==n.indexOf("array")?r[s.key.slice(-1)]=[]:"null"===e?r[s.key.slice(-1)]=null:delete r[s.key.slice(-1)]}}})),v()}})}}}])},l=function(r,n,i){i=e.isDefined(i)?i:!1,t.directive("sf"+e.uppercase(r[0])+r.substr(1),function(){return{restrict:"EAC",scope:!0,replace:!0,transclude:i,template:'',link:function(t,n,i){var o={items:"c",titleMap:"c",schema:"c"},a={type:r},l=!0;e.forEach(i,function(r,n){if("$"!==n[0]&&0!==n.indexOf("ng")&&"sfField"!==n){var s=function(r){e.isDefined(r)&&r!==a[n]&&(a[n]=r,l&&a.type&&(a.key||e.isUndefined(i.key))&&(t.form=a,l=!1))};"model"===n?t.$watch(r,function(e){e&&t.model!==e&&(t.model=e)}):"c"===o[n]?t.$watchCollection(r,s):i.$observe(n,s)}})}}})};this.createDecorator=function(t,r){i[t]={__name:t},e.forEach(r,function(e,r){i[t][r]={template:e,replace:!1,builder:[]}}),i[n]||(n=t),a(t)},this.defineDecorator=function(t,r){i[t]={__name:t},e.forEach(r,function(r,n){r.builder=r.builder||[],r.replace=e.isDefined(r.replace)?r.replace:!0,i[t][n]=r}),i[n]||(n=t),a(t)},this.createDirective=l,this.createDirectives=function(t){e.forEach(t,function(e,t){l(t,e)})},this.decorator=function(e){return e=e||n,i[e]},this.addMapping=function(e,t,r,n,o){i[e]&&(i[e][t]={template:r,builder:n,replace:!!o})},this.defineAddOn=function(e,t,r,n){i[e]&&(i[e][t]={template:r,builder:n,replace:!0})},this.$get=function(){return{decorator:function(e){return i[e]||i[n]},defaultDecorator:n}},a("sfDecorator")}]),e.module("schemaForm").provider("sfErrorMessage",function(){var t={"default":"Field does not validate",0:"Invalid type, expected {{schema.type}}",1:"No enum match for: {{viewValue}}",10:'Data does not match any schemas from "anyOf"',11:'Data does not match any schemas from "oneOf"',12:'Data is valid against more than one schema from "oneOf"',13:'Data matches schema from "not"',100:"Value is not a multiple of {{schema.multipleOf}}",101:"{{viewValue}} is less than the allowed minimum of {{schema.minimum}}",102:"{{viewValue}} is equal to the exclusive minimum {{schema.minimum}}",103:"{{viewValue}} is greater than the allowed maximum of {{schema.maximum}}",104:"{{viewValue}} is equal to the exclusive maximum {{schema.maximum}}",105:"Value is not a valid number",200:"String is too short ({{viewValue.length}} chars), minimum {{schema.minLength}}",201:"String is too long ({{viewValue.length}} chars), maximum {{schema.maxLength}}",202:"String does not match pattern: {{schema.pattern}}",300:"Too few properties defined, minimum {{schema.minProperties}}",301:"Too many properties defined, maximum {{schema.maxProperties}}",302:"Required",303:"Additional properties not allowed",304:"Dependency failed - key must exist",400:"Array is too short ({{value.length}}), minimum {{schema.minItems}}",401:"Array is too long ({{value.length}}), maximum {{schema.maxItems}}",402:"Array items are not unique",403:"Additional items not allowed",500:"Format validation failed",501:'Keyword failed: "{{title}}"',600:"Circular $refs",1e3:"Unknown property (not in schema)"};t.number=t[105],t.required=t[302],t.min=t[101],t.max=t[103],t.maxlength=t[201],t.minlength=t[200],t.pattern=t[202],this.setDefaultMessages=function(e){t=e},this.getDefaultMessages=function(){return t},this.setDefaultMessage=function(e,r){t[e]=r},this.$get=["$interpolate",function(r){var n={};return n.defaultMessages=t,n.interpolate=function(n,i,o,a,l){l=l||{};var s=a.validationMessage||{};0===n.indexOf("tv4-")&&(n=n.substring(4));var u=s["default"]||l["default"]||"";[s,l,t].some(function(t){return e.isString(t)||e.isFunction(t)?(u=t,!0):t&&t[n]?(u=t[n],!0):void 0});var c={error:n,value:i,viewValue:o,form:a,schema:a.schema,title:a.title||a.schema&&a.schema.title};return e.isFunction(u)?u(c):r(u)(c)},n}]}),e.module("schemaForm").provider("schemaForm",["sfPathProvider",function(t){var r=function(e){if(Array.isArray(e)&&2==e.length){if("null"===e[0])return e[1];if("null"===e[1])return e[0]}return e},n=function(e){var t=[];return e.forEach(function(e){t.push({name:e,value:e})}),t},i=function(t,r){if(!e.isArray(t)){var n=[];return r?e.forEach(r,function(e,r){n.push({name:t[e],value:e})}):e.forEach(t,function(e,t){n.push({name:e,value:t})}),n}return t},o=function(t,n,i){var o=h[r(n.type)];if(o)for(var a,l=0;l2&&t.test(o[1])?[]:{});for(var a=n[o[0]],l=1;l1&&(m={type:"section",items:l.items.map(function(t){return t.ngModelOptions=l.ngModelOptions,e.isUndefined(t.readonly)&&(t.readonly=l.readonly),t})})}if(a.copyWithIndex=function(t){if(!c[t]&&m){var n=e.copy(m);n.arrayIndex=t,r.traverseForm(n,o(t)),c[t]=n}return c[t]},a.appendToArray=function(){var n=s.length,i=a.copyWithIndex(n);if(r.traverseForm(i,function(r){if(r.key){var n;e.isDefined(r["default"])&&(n=r["default"]),e.isDefined(r.schema)&&e.isDefined(r.schema["default"])&&(n=r.schema["default"]),e.isDefined(n)&&t(r.key,a.model,n)}}),n===s.length){var o,u=t("schema.items.type",l);"object"===u?o={}:"array"===u&&(o=[]),s.push(o)}return a.validateArray(),s},a.deleteFromArray=function(e){return s.splice(e,1),a.validateArray(),u&&u.$setDirty&&u.$setDirty(),s},l.titleMap||l.startEmpty===!0||0!==s.length||a.appendToArray(),l.titleMap&&l.titleMap.length>0){a.titleMapValues=[];var p=function(e){a.titleMapValues=[],e=e||[],l.titleMap.forEach(function(t){a.titleMapValues.push(-1!==e.indexOf(t.value))})};p(a.modelArray),a.$watchCollection("modelArray",p),a.$watchCollection("titleMapValues",function(e,t){if(e&&e!==t){for(var r=a.modelArray;r.length>0;)r.pop();l.titleMap.forEach(function(t,n){e[n]&&r.push(t.value)}),a.validateArray()}})}if(u){var h;a.validateArray=function(){var e=n.validate(l,a.modelArray.length>0?a.modelArray:void 0);Object.keys(u.$error).filter(function(e){return 0===e.indexOf("tv4-")}).forEach(function(e){u.$setValidity(e,!0)}),e.valid!==!1||!e.error||""!==e.error.dataPath&&e.error.dataPath!=="/"+l.key[l.key.length-1]||(u.$setViewValue(a.modelArray),h=e.error,u.$setValidity("tv4-"+e.error.code,!1))},a.$on("schemaFormValidate",a.validateArray),a.hasSuccess=function(){return a.options&&a.options.pristine&&a.options.pristine.success===!1?u.$valid&&!u.$pristine&&!u.$isEmpty(u.$modelValue):u.$valid&&(!u.$pristine||!u.$isEmpty(u.$modelValue))},a.hasError=function(){return a.options&&a.options.pristine&&a.options.pristine.errors===!1?u.$invalid&&!u.$pristine:u.$invalid},a.schemaError=function(){return h}}f()}})}}}]),e.module("schemaForm").directive("sfChanged",function(){return{require:"ngModel",restrict:"AC",scope:!1,link:function(t,r,n,i){var o=t.$eval(n.sfChanged);o&&o.onChange&&i.$viewChangeListeners.push(function(){e.isFunction(o.onChange)?o.onChange(i.$modelValue,o):t.evalExpr(o.onChange,{modelValue:i.$modelValue,form:o})})}}}),e.module("schemaForm").directive("sfField",["$parse","$compile","$http","$templateCache","$interpolate","$q","sfErrorMessage","sfPath","sfSelect",function(t,r,n,i,o,a,l,s,u){return{restrict:"AE",replace:!1,transclude:!1,scope:!0,require:"^sfSchema",link:{pre:function(e,t,r,n){e.$on("schemaFormPropagateNgModelController",function(t,r){t.stopPropagation(),t.preventDefault(),e.ngModel=r}),e.form=n.lookup["f"+r.sfField]},post:function(t,r,n,i){t.showTitle=function(){return t.form&&t.form.notitle!==!0&&t.form.title},t.listToCheckboxValues=function(t){var r={};return e.forEach(t,function(e){r[e]=!0}),r},t.checkboxValuesToList=function(t){var r=[];return e.forEach(t,function(e,t){e&&r.push(t)}),r},t.buttonClick=function(r,n){e.isFunction(n.onClick)?n.onClick(r,n):e.isString(n.onClick)&&(i?i.evalInParentScope(n.onClick,{$event:r,form:n}):t.$eval(n.onClick,{$event:r,form:n}))},t.evalExpr=function(e,r){return i?i.evalInParentScope(e,r):t.$eval(e,r)},t.evalInScope=function(e,r){return e?t.$eval(e,r):void 0},t.interp=function(e,t){return e&&o(e)(t)},t.hasSuccess=function(){return t.ngModel?t.options&&t.options.pristine&&t.options.pristine.success===!1?t.ngModel.$valid&&!t.ngModel.$pristine&&!t.ngModel.$isEmpty(t.ngModel.$modelValue):t.ngModel.$valid&&(!t.ngModel.$pristine||!t.ngModel.$isEmpty(t.ngModel.$modelValue)):!1},t.hasError=function(){return t.ngModel?t.options&&t.options.pristine&&t.options.pristine.errors===!1?t.ngModel.$invalid&&!t.ngModel.$pristine:t.ngModel.$invalid:!1},t.errorMessage=function(e){return l.interpolate(e&&e.code+""||"default",t.ngModel&&t.ngModel.$modelValue||"",t.ngModel&&t.ngModel.$viewValue||"",t.form,t.options&&t.options.validationMessage)};var a=t.form;a.key&&(t.$on("schemaForm.error."+a.key.join("."),function(e,r,n,i){(n===!0||n===!1)&&(i=n,n=void 0),t.ngModel&&r&&(t.ngModel.$setDirty?t.ngModel.$setDirty():(t.ngModel.$dirty=!0,t.ngModel.$pristine=!1),n&&(a.validationMessage||(a.validationMessage={}),a.validationMessage[r]=n),t.ngModel.$setValidity(r,i===!0),i===!0&&(t.ngModel.$validate(),t.$broadcast("schemaFormValidate")))}),t.$on("$destroy",function(){if(!t.externalDestructionInProgress){var e=a.destroyStrategy||t.options&&t.options.destroyStrategy||"remove";if(a.key&&"retain"!==e){var r=t.model;if(a.key.length>1&&(r=u(a.key.slice(0,a.key.length-1),r)),void 0===r)return;var n=a.schema&&a.schema.type||"";"empty"===e&&-1!==n.indexOf("string")?r[a.key.slice(-1)]="":"empty"===e&&-1!==n.indexOf("object")?r[a.key.slice(-1)]={}:"empty"===e&&-1!==n.indexOf("array")?r[a.key.slice(-1)]=[]:"null"===e?r[a.key.slice(-1)]=null:delete r[a.key.slice(-1)]}}}))}}}}]),e.module("schemaForm").directive("sfMessage",["$injector","sfErrorMessage",function(t,r){var n=t.has("$sanitize")?t.get("$sanitize"):function(e){return e};return{scope:!1,restrict:"EA",link:function(t,i,o){var a="";o.sfMessage&&t.$watch(o.sfMessage,function(e){e&&(a=n(e),u(!!t.ngModel))});var l,s=function(e){e!==l&&(i.html(e),l=e)},u=function(n){if(n)if(t.hasError()){var i=[];e.forEach(t.ngModel&&t.ngModel.$error,function(e,t){e&&i.push(t)}),i=i.filter(function(e){return"schemaForm"!==e});var o=i[0];s(o?r.interpolate(o,t.ngModel.$modelValue,t.ngModel.$viewValue,t.form,t.options&&t.options.validationMessage):a)}else s(a);else s(a)};u();var c=t.$watch("ngModel",function(e){e&&(e.$parsers.push(function(e){return u(!0),e}),e.$formatters.push(function(e){return u(!0),e}),c())});t.$watchCollection("ngModel.$error",function(){u(!!t.ngModel)})}}}]),e.module("schemaForm").directive("sfNewArray",["sfSelect","sfPath","schemaForm",function(t,r,n){return{scope:!1,link:function(i,o,a){i.min=0,i.modelArray=i.$eval(a.sfNewArray);var l=function(){i.modelArray=i.$eval(a.sfNewArray),(!(i.ngModel&&i.ngModel.$pristine&&i.firstDigest)||i.options&&i.options.validateOnRender===!0)&&i.validateField&&i.validateField()},s=function(){i.form&&i.form.onChange&&(e.isFunction(i.form.onChange)?i.form.onChange(i.modelArray,i.form):i.evalExpr(i.form.onChange,{modelValue:i.modelArray,form:i.form}))},u=function(){var e=i.modelArray;if(!e){var n=r.parse(a.sfNewArray);e=[],t(n,i,e),i.modelArray=e}return e},c=i.$watch("form",function(e){if(e){if(e.titleMap||e.startEmpty===!0||i.modelArray&&0!==i.modelArray.length||i.appendToArray(),i.form&&i.form.schema&&i.form.schema.uniqueItems===!0?(i.$watch(a.sfNewArray,l,!0),i.$watch([a.sfNewArray,a.sfNewArray+".length"],s)):i.$watchGroup?i.$watchGroup([a.sfNewArray,a.sfNewArray+".length"],function(){l(),s()}):(i.$watch(a.sfNewArray,function(){l(),s()}),i.$watch(a.sfNewArray+".length",function(){l(),s()})),e.titleMap&&e.titleMap.length>0){i.titleMapValues=[];var t=function(t){i.titleMapValues=[],t=t||[],e.titleMap.forEach(function(e){i.titleMapValues.push(-1!==t.indexOf(e.value))})};t(i.modelArray),i.$watchCollection("modelArray",t),i.$watchCollection("titleMapValues",function(t,r){if(t&&t!==r){for(var n=u();n.length>0;)n.pop();e.titleMap.forEach(function(e,r){t[r]&&n.push(e.value)}),i.validateField&&i.validateField()}})}c()}});i.appendToArray=function(){var r,o=u();if(i.form&&i.form.schema&&i.form.schema.items){var a=i.form.schema.items;a.type&&-1!==a.type.indexOf("object")?(r={},i.options&&i.options.setSchemaDefaults===!1||(r=e.isDefined(a["default"])?a["default"]:r,r&&n.traverseSchema(a,function(n,i){e.isDefined(n["default"])&&t(i,r,n["default"])}))):a.type&&-1!==a.type.indexOf("array")?(r=[],i.options&&i.options.setSchemaDefaults===!1||(r=a["default"]||r)):i.options&&i.options.setSchemaDefaults===!1||(r=a["default"]||r)}return o.push(r),o},i.deleteFromArray=function(e){var t=i.modelArray;return t&&t.splice(e,1),t};var f=function(e){return function(t){t.key&&(t.key[t.key.indexOf("")]=e)}},d={};i.copyWithIndex=function(t){var r=i.form;if(!d[t]){var o=r.items[0];if(r.items.length>1&&(o={type:"section",items:r.items.map(function(t){return t.ngModelOptions=r.ngModelOptions,e.isUndefined(t.readonly)&&(t.readonly=r.readonly),t})}),o){var a=e.copy(o);a.arrayIndex=t,n.traverseForm(a,f(t)),d[t]=a}}return d[t]}}}}]),e.module("schemaForm").directive("sfSchema",["$compile","$http","$templateCache","$q","schemaForm","schemaFormDecorators","sfSelect","sfPath","sfBuilder",function(t,r,n,i,o,a,l,s,u){return{scope:{schema:"=sfSchema",initialForm:"=sfForm",model:"=sfModel",options:"=sfOptions"},controller:["$scope",function(e){this.evalInParentScope=function(t,r){return e.$parent.$eval(t,r)};var t=this;e.lookup=function(e){return e&&(t.lookup=e),t.lookup}}],replace:!1,restrict:"A",transclude:!0,require:"?form",link:function(s,c,f,d,m){s.formCtrl=d;var p={};m(s,function(e){if(e.addClass("schema-form-ignore"),c.prepend(e),c[0].querySelectorAll){var t=c[0].querySelectorAll("[ng-model]");if(t)for(var r=0;r0?i.all(a.map(function(e){return r.get(e.templateUrl,{cache:n}).then(function(t){e.template=t.data})})).then(function(){g(e,t,l)}):g(e,t,l)},g=function(r,n,i){h&&(s.externalDestructionInProgress=!0,h.$destroy(),s.externalDestructionInProgress=!1),h=s.$new(),h.schemaForm={form:i,schema:r},c.children(":not(.schema-form-ignore)").remove();for(var d={},m=c[0].querySelectorAll("*[sf-insert-field]"),p=0;p0&&(v.schema=e,v.form=t,y(e,t))}),s.$on("schemaFormRedraw",function(){var t=s.schema,r=s.initialForm?e.copy(s.initialForm):["*"];t&&y(t,r)}),s.$on("$destroy",function(){s.externalDestructionInProgress=!0}),s.evalExpr=function(e,t){return s.$parent.$eval(e,t)}}}}]),e.module("schemaForm").directive("schemaValidate",["sfValidator","$parse","sfSelect",function(t,r,n){return{restrict:"A",scope:!1,priority:500,require:"ngModel",link:function(r,i,o,a){r.$emit("schemaFormPropagateNgModelController",a);var l=null,s=r.$eval(o.schemaValidate);s.copyValueTo&&a.$viewChangeListeners.push(function(){var t=s.copyValueTo;e.forEach(t,function(e){n(e,r.model,a.$modelValue)})});var u=function(e){if(!s)return e;if(r.options&&r.options.tv4Validation===!1)return e;var n=t.validate(s,e);return Object.keys(a.$error).filter(function(e){return 0===e.indexOf("tv4-")}).forEach(function(e){a.$setValidity(e,!0)}),n.valid?e:(a.$setValidity("tv4-"+n.error.code,!1),l=n.error,a.$validators?e:void 0)};"function"==typeof s.ngModel&&s.ngModel(a),["$parsers","$viewChangeListeners","$formatters"].forEach(function(e){s[e]&&a[e]&&s[e].forEach(function(t){a[e].push(t)})}),["$validators","$asyncValidators"].forEach(function(t){s[t]&&a[t]&&e.forEach(s[t],function(e,r){a[t][r]=e})}),a.$parsers.push(u),a.$validators&&(a.$validators.schemaForm=function(){return!Object.keys(a.$error).some(function(e){return"schemaForm"!==e})});var c=s.schema;r.validateField=function(e){(void 0==e||a.$$parentForm.$name===e)&&(c&&-1!==c.type.indexOf("array")&&u(a.$modelValue),a.$setDirty?(a.$setDirty(),a.$setViewValue(a.$viewValue),a.$commitViewValue(),s.required&&a.$isEmpty(a.$modelValue)&&a.$setValidity("tv4-302",!1)):a.$setViewValue(a.$viewValue))},a.$formatters.push(function(e){return!a.$pristine||!r.firstDigest||r.options&&r.options.validateOnRender===!0?(u(a.$modelValue),e):e}),r.$on("schemaFormValidate",function(e,t){r.validateField(t)}),r.schemaError=function(){return l}}}}]),o}); \ No newline at end of file diff --git a/gulp/tasks/jscs.js b/gulp/tasks/jscs.js index 8d1bfef9a..4f6992f6e 100644 --- a/gulp/tasks/jscs.js +++ b/gulp/tasks/jscs.js @@ -1,5 +1,5 @@ -var gulp = require('gulp'), - jscs = require('gulp-jscs'); +var gulp = require('gulp'); +var jscs = require('gulp-jscs'); gulp.task('jscs', function() { gulp.src('./src/**/*.js') diff --git a/gulp/tasks/minify.js b/gulp/tasks/minify.js index 9cc164ef6..cca63e90e 100644 --- a/gulp/tasks/minify.js +++ b/gulp/tasks/minify.js @@ -1,30 +1,30 @@ -var gulp = require('gulp'), - concat = require('gulp-concat'), - rename = require('gulp-rename'), - umd = require('gulp-umd'), - uglify = require('gulp-uglify'); +var gulp = require('gulp'); +var concat = require('gulp-concat'); +var rename = require('gulp-rename'); +var umd = require('gulp-umd'); +var uglify = require('gulp-uglify'); gulp.task('minify', function() { gulp.src([ - './src/module.js', - './src/sfPath.js', - './src/services/*.js', - './src/directives/*.js' + './src/module.js', + './src/sfPath.js', + './src/services/*.js', + './src/directives/*.js' ]) - .pipe(concat('schema-form.js')) - .pipe(umd({ - dependencies: function() { - return [ - {name: 'angular'}, - {name: 'objectpath'}, - {name: 'tv4'}, - ]; - }, - exports: function() {return 'schemaForm';}, - namespace: function() {return 'schemaForm';} + .pipe(concat('schema-form.js')) + .pipe(umd({ + dependencies: function() { + return [ + {name: 'angular'}, + {name: 'objectpath'}, + {name: 'tv4'}, + ]; + }, + exports: function() {return 'schemaForm';}, + namespace: function() {return 'schemaForm';} })) - .pipe(gulp.dest('./dist/')) - .pipe(uglify()) - .pipe(rename('schema-form.min.js')) - .pipe(gulp.dest('./dist/')); + .pipe(gulp.dest('./dist/')) + .pipe(uglify()) + .pipe(rename('schema-form.min.js')) + .pipe(gulp.dest('./dist/')); }); diff --git a/package.json b/package.json index 4eb1779ed..6f5df7a37 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-schema-form", - "version": "0.8.12", + "version": "0.8.13", "description": "Create complex forms from a JSON schema with angular.", "repository": "Textalk/angular-schema-form", "main": "dist/schema-form.min.js", @@ -14,7 +14,8 @@ "David Jensen (https://github.com/davidlgj)", "Denis Dervisevic (https://github.com/Dervisevic)", "Cameron Edwards (https://github.com/cameronprattedwards)", - "Mike Marcacci (https://github.com/mike-marcacci)" + "Mike Marcacci (https://github.com/mike-marcacci)", + "Marcel Bennett (https://github.com/Anthropic)" ], "license": "MIT", "dependencies": { @@ -29,6 +30,8 @@ "form", "json", "json-schema", + "json-schema-form", + "ui-schema", "schema" ], "devDependencies": { @@ -68,9 +71,7 @@ "basePath": "/dist/", "files": [ "schema-form.min.js", - "schema-form.js", - "bootstrap-decorator.min.js", - "bootstrap-decorator.js" + "schema-form.js" ] } }