From fe2a7b59a6cf4e41bfb69d5738e3c4029d26065f Mon Sep 17 00:00:00 2001 From: Jon Stevens Date: Fri, 3 Jul 2015 18:29:32 -0700 Subject: [PATCH 001/106] small typo fixes --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 99bc998cc..6dd618ab2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -361,7 +361,7 @@ scope.$broadcast('schemaForm.error.name','usernameAlreadyTaken','The username is This will invalidate the field and therefore the form and show the error message where it normally pops up, under the field for instance. -There is a catch though, schema form can't now when this field is valid s you have to tell it by +There is a catch though, schema form can't know when this field is valid so you have to tell it by sending an event again, this time switch out the validation message for validity of the field, i.e. `true`. From 433b4d97980b84b7f9c94632c14c48ceedc0aff3 Mon Sep 17 00:00:00 2001 From: Greg Barrett Date: Tue, 7 Jul 2015 11:15:14 -0400 Subject: [PATCH 002/106] Anyplace where there is a DOM field ID or NAME, or a label element FOR, change it to use a fully qualified value. For IDs and FORs, join the key components with a '-', and for NAMEs, join key components with a '.'. Previously, only the last component of the form.key was used to build these values (in most cases); the problem with that is when you have multiple leaves in your model with the same name for the final component. In these cases, if you click on the label for any of these fields, focus is given to the first one instead of the current one as was probably intended. Also, IDs are intended to be unique within the page DOM. --- .../decorators/bootstrap/checkbox.html | 2 +- .../decorators/bootstrap/checkboxes.html | 2 +- src/directives/decorators/bootstrap/default.html | 16 ++++++++-------- src/directives/decorators/bootstrap/select.html | 2 +- .../decorators/bootstrap/textarea.html | 10 +++++----- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/directives/decorators/bootstrap/checkbox.html b/src/directives/decorators/bootstrap/checkbox.html index d6ad64d4b..37d52760e 100644 --- a/src/directives/decorators/bootstrap/checkbox.html +++ b/src/directives/decorators/bootstrap/checkbox.html @@ -8,7 +8,7 @@ ng-model-options="form.ngModelOptions" schema-validate="form" class="{{form.fieldHtmlClass}}" - name="{{form.key.slice(-1)[0]}}"> + name="{{form.key.join('.')}}">
diff --git a/src/directives/decorators/bootstrap/checkboxes.html b/src/directives/decorators/bootstrap/checkboxes.html index 45b514135..549d0c7b5 100644 --- a/src/directives/decorators/bootstrap/checkboxes.html +++ b/src/directives/decorators/bootstrap/checkboxes.html @@ -9,7 +9,7 @@ sf-changed="form" class="{{form.fieldHtmlClass}}" ng-model="titleMapValues[$index]" - name="{{form.key.slice(-1)[0]}}"> + name="{{form.key.join('.')}}"> diff --git a/src/directives/decorators/bootstrap/default.html b/src/directives/decorators/bootstrap/default.html index b5685042b..9402713ce 100644 --- a/src/directives/decorators/bootstrap/default.html +++ b/src/directives/decorators/bootstrap/default.html @@ -1,6 +1,6 @@
- + + name="{{form.key.join('.')}}" + aria-describedby="{{form.key.join('-') + 'Status'}}">
@@ -28,13 +28,13 @@ sf-changed="form" placeholder="{{form.placeholder}}" class="form-control {{form.fieldHtmlClass}}" - id="{{form.key.slice(-1)[0]}}" + id="{{form.key.join('-')}}" ng-model-options="form.ngModelOptions" ng-model="$$value$$" ng-disabled="form.readonly" schema-validate="form" - name="{{form.key.slice(-1)[0]}}" - aria-describedby="{{form.key.slice(-1)[0] + 'Status'}}"> + name="{{form.key.join('.')}}" + aria-describedby="{{form.key.join('-') + 'Status'}}"> {{ hasSuccess() ? '(success)' : '(error)' }}
diff --git a/src/directives/decorators/bootstrap/select.html b/src/directives/decorators/bootstrap/select.html index dbefbd208..56eb60f4d 100644 --- a/src/directives/decorators/bootstrap/select.html +++ b/src/directives/decorators/bootstrap/select.html @@ -10,7 +10,7 @@ class="form-control {{form.fieldHtmlClass}}" schema-validate="form" ng-options="item.value as item.name group by item.group for item in form.titleMap track by item[form.trackBy]" - name="{{form.key.slice(-1)[0]}}"> + name="{{form.key.join('.')}}">
diff --git a/src/directives/decorators/bootstrap/textarea.html b/src/directives/decorators/bootstrap/textarea.html index 06364edfc..b7212d309 100644 --- a/src/directives/decorators/bootstrap/textarea.html +++ b/src/directives/decorators/bootstrap/textarea.html @@ -1,16 +1,16 @@
- + + name="{{form.key.join('.')}}">
@@ -18,14 +18,14 @@ class="input-group-addon" ng-bind-html="form.fieldAddonLeft"> + name="{{form.key.join('.')}}"> From fbc33ec2cc09bad80b9fad7d0670d4ea81bbef90 Mon Sep 17 00:00:00 2001 From: Greg Barrett Date: Tue, 7 Jul 2015 12:03:56 -0400 Subject: [PATCH 003/106] Compiled files. --- dist/bootstrap-decorator.js | 10 +++++----- dist/bootstrap-decorator.min.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dist/bootstrap-decorator.js b/dist/bootstrap-decorator.js index a6cc5f3e1..da15f19fc 100644 --- a/dist/bootstrap-decorator.js +++ b/dist/bootstrap-decorator.js @@ -1,9 +1,9 @@ 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","

{{ form.title }}

"); -$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/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","
"); @@ -11,11 +11,11 @@ $templateCache.put("directives/decorators/bootstrap/radio-buttons.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/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","
");}]); +$templateCache.put("directives/decorators/bootstrap/textarea.html","
");}]); angular.module('schemaForm').config(['schemaFormDecoratorsProvider', function(decoratorsProvider) { var base = 'directives/decorators/bootstrap/'; diff --git a/dist/bootstrap-decorator.min.js b/dist/bootstrap-decorator.min.js index 316b4d431..018463afe 100644 --- a/dist/bootstrap-decorator.min.js +++ b/dist/bootstrap-decorator.min.js @@ -1 +1 @@ -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",'

{{ form.title }}

'),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)}}}); \ No newline at end of file +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",'

{{ form.title }}

'),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,a){e.title=e.$eval(a.title)}}}); \ No newline at end of file From cfe0adda90117730a4489a0982fa06ccb92e938a Mon Sep 17 00:00:00 2001 From: David Jensen Date: Wed, 19 Aug 2015 08:29:16 +0200 Subject: [PATCH 004/106] CHANGELOG and version bump --- CHANGELOG | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 71ffe35c1..1ef8af542 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,10 @@ +v0.8.7 +------ + * Moved common builder functions from angular-schema-form-bootstrap decorator. + * Bugfx for the new builder. + v0.8.6 +------ * Removed left over console.timeEnd v0.8.5 diff --git a/package.json b/package.json index c836bc7d8..cfd301321 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-schema-form", - "version": "0.8.6", + "version": "0.8.7", "description": "Create complex forms from a JSON schema with angular.", "repository": "Textalk/angular-schema-form", "main": "dist/schema-form.min.js", From 7016113de97581d916b699657ce54c981756216e Mon Sep 17 00:00:00 2001 From: David Jensen Date: Thu, 20 Aug 2015 09:52:34 +0200 Subject: [PATCH 005/106] New builder: defaults when adding to array. --- src/directives/newArray.js | 50 ++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/src/directives/newArray.js b/src/directives/newArray.js index d747c7c33..bd9a58cee 100644 --- a/src/directives/newArray.js +++ b/src/directives/newArray.js @@ -129,28 +129,52 @@ function(sel, sfPath, schemaForm) { }); scope.appendToArray = function() { - var empty; - // Same old add empty things to the array hack :( - if (scope.form && scope.form.schema) { - if (scope.form.schema.items) { - if (scope.form.schema.items.type === 'object') { - empty = {}; - } else if (scope.form.schema.items.type === 'array') { - empty = []; - } - } - } - + // Create and set an array if needed. var model = scope.modelArray; if (!model) { - // Create and set an array if needed. var selection = sfPath.parse(attrs.sfNewArray); model = []; sel(selection, scope, model); scope.modelArray = model; } + + // Same old add empty things to the array hack :( + if (scope.form && scope.form.schema && scope.form.schema.items) { + + var items = scope.form.schema.items; + if (items.type && items.type.indexOf('object') !== -1) { + empty = {}; + + // Check for possible defaults + if (!scope.options || scope.options.setSchemaDefaults !== false) { + empty = angular.isDefined(items['default']) ? items['default'] : empty; + + // Check for defaults further down in the schema. + // If the default instance sets the new array item to something falsy, i.e. null + // then there is no need to go further down. + if (empty) { + schemaForm.traverseSchema(items, function(prop, path) { + if (angular.isDefined(prop['default'])) { + sel(path, empty, prop['default']); + } + }); + } + } + + } else if (items.type && items.type.indexOf('array') !== -1) { + empty = []; + if (!scope.options || scope.options.setSchemaDefaults !== false) { + empty = items['default'] || empty; + } + } else { + // No type? could still have defaults. + if (!scope.options || scope.options.setSchemaDefaults !== false) { + empty = items['default'] || empty; + } + } + } model.push(empty); return model; From a103ad17ea498a1d90236fd7e93dd79a853ff5d6 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Thu, 20 Aug 2015 09:53:06 +0200 Subject: [PATCH 006/106] Bugfix, don't rely on documentFragment.children --- src/services/builder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/builder.js b/src/services/builder.js index b825aa099..0d4724d79 100644 --- a/src/services/builder.js +++ b/src/services/builder.js @@ -113,7 +113,7 @@ angular.module('schemaForm').provider('sfBuilder', ['sfPathProvider', function(s '"modelValue": model' + (strKey[0] === '[' ? '' : '.') + strKey + '})'; } - var children = args.fieldFrag.children; + var children = args.fieldFrag.children || args.fieldFrag.childNodes; for (var i = 0; i < children.length; i++) { var child = children[i]; var ngIf = child.getAttribute('ng-if'); From ae7b1f27070af873a7c1f67f77d5abb8450765bd Mon Sep 17 00:00:00 2001 From: David Jensen Date: Thu, 20 Aug 2015 16:03:12 +0200 Subject: [PATCH 007/106] Fix for template type in the new builder --- dist/schema-form.js | 108 ++++++++++++++++++++++++++-------- dist/schema-form.min.js | 2 +- src/directives/schema-form.js | 25 +++++++- src/services/builder.js | 18 ++++-- src/services/schema-form.js | 13 +++- 5 files changed, 127 insertions(+), 39 deletions(-) diff --git a/dist/schema-form.js b/dist/schema-form.js index 3e4387908..0796a752f 100644 --- a/dist/schema-form.js +++ b/dist/schema-form.js @@ -255,7 +255,7 @@ angular.module('schemaForm').provider('sfBuilder', ['sfPathProvider', function(s '"modelValue": model' + (strKey[0] === '[' ? '' : '.') + strKey + '})'; } - var children = args.fieldFrag.children; + var children = args.fieldFrag.children || args.fieldFrag.childNodes; for (var i = 0; i < children.length; i++) { var child = children[i]; var ngIf = child.getAttribute('ng-if'); @@ -351,7 +351,7 @@ angular.module('schemaForm').provider('sfBuilder', ['sfPathProvider', function(s // measure optmization. A good start is probably a cache of DOM nodes for a particular // template that can be cloned instead of using innerHTML var div = document.createElement('div'); - var template = templateFn(field.template) || templateFn([decorator['default'].template]); + var template = templateFn(f, field) || templateFn(f, decorator['default']); div.innerHTML = template; // Move node to a document fragment, we don't want the div. @@ -375,11 +375,14 @@ angular.module('schemaForm').provider('sfBuilder', ['sfPathProvider', function(s }; + // Let the form definiton override builders if it wants to. + var builderFn = f.builder || field.builder; + // Builders are either a function or a list of functions. - if (typeof field.builder === 'function') { - field.builder(args); + if (typeof builderFn === 'function') { + builderFn(args); } else { - field.builder.forEach(function(fn) { fn(args); }); + builderFn.forEach(function(fn) { fn(args); }); } // Append @@ -396,8 +399,11 @@ angular.module('schemaForm').provider('sfBuilder', ['sfPathProvider', function(s * Builds a form from a canonical form definition */ build: function(form, decorator, slots, lookup) { - return build(form, decorator, function(url) { - return $templateCache.get(url); + return build(form, decorator, function(form, field) { + if (form.type === 'template') { + return form.template; + } + return $templateCache.get(field.template); }, slots, undefined, undefined, lookup); }, @@ -1324,7 +1330,7 @@ angular.module('schemaForm').provider('schemaForm', var service = {}; - service.merge = function(schema, form, ignore, options, readonly) { + service.merge = function(schema, form, ignore, options, readonly, asyncTemplates) { form = form || ['*']; options = options || {}; @@ -1395,13 +1401,13 @@ angular.module('schemaForm').provider('schemaForm', //if it's a type with items, merge 'em! if (obj.items) { - obj.items = service.merge(schema, obj.items, ignore, options, obj.readonly); + obj.items = service.merge(schema, obj.items, ignore, options, obj.readonly, asyncTemplates); } //if its has tabs, merge them also! if (obj.tabs) { angular.forEach(obj.tabs, function(tab) { - tab.items = service.merge(schema, tab.items, ignore, options, obj.readonly); + tab.items = service.merge(schema, tab.items, ignore, options, obj.readonly, asyncTemplates); }); } @@ -1410,6 +1416,13 @@ angular.module('schemaForm').provider('schemaForm', if (obj.type === 'checkbox' && angular.isUndefined(obj.schema['default'])) { obj.schema['default'] = false; } + + // Special case: template type with tempplateUrl that's needs to be loaded before rendering + // TODO: this is not a clean solution. Maybe something cleaner can be made when $ref support + // is introduced since we need to go async then anyway + if (asyncTemplates && obj.type === 'template' && !obj.template && obj.templateUrl) { + asyncTemplates.push(obj); + } return obj; })); @@ -2281,28 +2294,52 @@ function(sel, sfPath, schemaForm) { }); scope.appendToArray = function() { - var empty; - // Same old add empty things to the array hack :( - if (scope.form && scope.form.schema) { - if (scope.form.schema.items) { - if (scope.form.schema.items.type === 'object') { - empty = {}; - } else if (scope.form.schema.items.type === 'array') { - empty = []; - } - } - } - + // Create and set an array if needed. var model = scope.modelArray; if (!model) { - // Create and set an array if needed. var selection = sfPath.parse(attrs.sfNewArray); model = []; sel(selection, scope, model); scope.modelArray = model; } + + // Same old add empty things to the array hack :( + if (scope.form && scope.form.schema && scope.form.schema.items) { + + var items = scope.form.schema.items; + if (items.type && items.type.indexOf('object') !== -1) { + empty = {}; + + // Check for possible defaults + if (!scope.options || scope.options.setSchemaDefaults !== false) { + empty = angular.isDefined(items['default']) ? items['default'] : empty; + + // Check for defaults further down in the schema. + // If the default instance sets the new array item to something falsy, i.e. null + // then there is no need to go further down. + if (empty) { + schemaForm.traverseSchema(items, function(prop, path) { + if (angular.isDefined(prop['default'])) { + sel(path, empty, prop['default']); + } + }); + } + } + + } else if (items.type && items.type.indexOf('array') !== -1) { + empty = []; + if (!scope.options || scope.options.setSchemaDefaults !== false) { + empty = items['default'] || empty; + } + } else { + // No type? could still have defaults. + if (!scope.options || scope.options.setSchemaDefaults !== false) { + empty = items['default'] || empty; + } + } + } model.push(empty); return model; @@ -2369,8 +2406,8 @@ FIXME: real documentation angular.module('schemaForm') .directive('sfSchema', -['$compile', 'schemaForm', 'schemaFormDecorators', 'sfSelect', 'sfPath', 'sfBuilder', - function($compile, schemaForm, schemaFormDecorators, sfSelect, sfPath, sfBuilder) { +['$compile', '$http', '$templateCache', '$q','schemaForm', 'schemaFormDecorators', 'sfSelect', 'sfPath', 'sfBuilder', + function($compile, $http, $templateCache, $q, schemaForm, schemaFormDecorators, sfSelect, sfPath, sfBuilder) { return { scope: { @@ -2429,8 +2466,27 @@ angular.module('schemaForm') // Common renderer function, can either be triggered by a watch or by an event. var render = function(schema, form) { - var merged = schemaForm.merge(schema, form, ignore, scope.options); + var asyncTemplates = []; + var merged = schemaForm.merge(schema, form, ignore, scope.options, undefined, asyncTemplates); + + if (asyncTemplates.length > 0) { + // Pre load all async templates and put them on the form for the builder to use. + $q.all(asyncTemplates.map(function(form) { + return $http.get(form.templateUrl, {cache: $templateCache}).then(function(res) { + form.template = res.data; + }); + })).then(function() { + internalRender(schema, form, merged); + }); + + } else { + internalRender(schema, form, merged); + } + + + }; + var internalRender = function(schema, form, merged) { // Create a new form and destroy the old one. // Not doing keeps old form elements hanging around after // they have been removed from the DOM diff --git a/dist/schema-form.min.js b/dist/schema-form.min.js index d9662f710..a56e0cfa4 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;)d.appendChild(p.childNodes[0]);var y={fieldFrag:d,form:c,lookup:u,state:s,path:l+"["+f+"]",build:function(e,n,i){return a(e,t,r,o,n,i,u)}};"function"==typeof m.builder?m.builder(y):m.builder.forEach(function(e){e(y)}),(i(c,o)||e).appendChild(d)}else{var v=document.createElement(n(t.__name,"-"));s.arrayCompatFlag?v.setAttribute("form","copyWithIndex($index)"):v.setAttribute("form",l+"["+f+"]"),(i(c,o)||e).appendChild(v)}return e}},c),c};return{build:function(t,r,n,i){return a(t,r,function(t){return e.get(t)},n,void 0,void 0,i)},builder:o,internalBuild:a}}]}]),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,m){return{restrict:"AE",replace:!1,transclude:!1,scope:!0,require:"?^sfSchema",link:function(t,d,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 y=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(d.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(d.children(),function(e){var t=e.getAttribute("ng-if");e.setAttribute("ng-if",t?"("+t+") || ("+a+")":a)})}i(d.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=m(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)]}}})),y()}})}}}])},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.$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&&(d={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]&&d){var n=e.copy(d);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 u.$valid&&!u.$pristine},a.hasError=function(){return 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.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 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),t.ngModel?l(t.ngModel.$valid):l())});var l=function(n){if(n&&!t.hasError())i.html(a);else{var o=[];e.forEach(t.ngModel&&t.ngModel.$error||{},function(e,t){e&&o.push(t)}),o=o.filter(function(e){return"schemaForm"!==e});var l=o[0];l?i.html(r.interpolate(l,t.ngModel.$modelValue,t.ngModel.$viewValue,t.form,t.options&&t.options.validationMessage)):i.html(a)}};l(),t.$watchCollection("ngModel.$error",function(){t.ngModel&&l(t.ngModel.$valid)})}}}]),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.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=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=i.modelArray;n.length>0;)n.pop();e.titleMap.forEach(function(e,r){t[r]&&n.push(e.value)}),i.validateField&&i.validateField()}})}u()}});i.appendToArray=function(){var e;i.form&&i.form.schema&&i.form.schema.items&&("object"===i.form.schema.items.type?e={}:"array"===i.form.schema.items.type&&(e=[]));var n=i.modelArray;if(!n){var o=r.parse(a.sfNewArray);n=[],t(o,i,n),i.modelArray=n}return n.push(e),n},i.deleteFromArray=function(e){var t=i.modelArray;return t&&t.splice(e,1),t};var c=function(e){return function(t){t.key&&(t.key[t.key.indexOf("")]=e)}},f={};i.copyWithIndex=function(t){var r=i.form;if(!f[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,c(t)),f[t]=a}}return f[t]}}}}]),e.module("schemaForm").directive("sfSchema",["$compile","schemaForm","schemaFormDecorators","sfSelect","sfPath","sfBuilder",function(t,r,n,i,o,a){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(o,l,s,u,c){o.formCtrl=u;var f={};c(o,function(e){if(e.addClass("schema-form-ignore"),l.prepend(e),l[0].querySelectorAll){var t=l[0].querySelectorAll("[ng-model]");if(t)for(var r=0;r0&&(d.schema=e,d.form=t,p(e,t))}),o.$on("schemaFormRedraw",function(){var e=o.schema,t=o.initialForm||["*"];e&&p(e,t)}),o.$on("$destroy",function(){o.externalDestructionInProgress=!0}),o.evalExpr=function(e,t){return o.$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)},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").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;)d.appendChild(p.childNodes[0]);var y={fieldFrag:d,form:c,lookup:u,state:s,path:l+"["+f+"]",build:function(e,n,i){return a(e,t,r,o,n,i,u)}},v=c.builder||m.builder;"function"==typeof v?v(y):v.forEach(function(e){e(y)}),(i(c,o)||e).appendChild(d)}else{var g=document.createElement(n(t.__name,"-"));s.arrayCompatFlag?g.setAttribute("form","copyWithIndex($index)"):g.setAttribute("form",l+"["+f+"]"),(i(c,o)||e).appendChild(g)}return e}},c),c};return{build:function(t,r,n,i){return a(t,r,function(t,r){return"template"===t.type?t.template:e.get(r.template)},n,void 0,void 0,i)},builder:o,internalBuild:a}}]}]),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,m){return{restrict:"AE",replace:!1,transclude:!1,scope:!0,require:"?^sfSchema",link:function(t,d,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 y=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(d.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(d.children(),function(e){var t=e.getAttribute("ng-if");e.setAttribute("ng-if",t?"("+t+") || ("+a+")":a)})}i(d.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=m(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)]}}})),y()}})}}}])},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.$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&&(d={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]&&d){var n=e.copy(d);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 u.$valid&&!u.$pristine},a.hasError=function(){return 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.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 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),t.ngModel?l(t.ngModel.$valid):l())});var l=function(n){if(n&&!t.hasError())i.html(a);else{var o=[];e.forEach(t.ngModel&&t.ngModel.$error||{},function(e,t){e&&o.push(t)}),o=o.filter(function(e){return"schemaForm"!==e});var l=o[0];l?i.html(r.interpolate(l,t.ngModel.$modelValue,t.ngModel.$viewValue,t.form,t.options&&t.options.validationMessage)):i.html(a)}};l(),t.$watchCollection("ngModel.$error",function(){t.ngModel&&l(t.ngModel.$valid)})}}}]),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.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=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=i.modelArray;n.length>0;)n.pop();e.titleMap.forEach(function(e,r){t[r]&&n.push(e.value)}),i.validateField&&i.validateField()}})}u()}});i.appendToArray=function(){var o,l=i.modelArray;if(!l){var s=r.parse(a.sfNewArray);l=[],t(s,i,l),i.modelArray=l}if(i.form&&i.form.schema&&i.form.schema.items){var u=i.form.schema.items;u.type&&-1!==u.type.indexOf("object")?(o={},i.options&&i.options.setSchemaDefaults===!1||(o=e.isDefined(u["default"])?u["default"]:o,o&&n.traverseSchema(u,function(r,n){e.isDefined(r["default"])&&t(n,o,r["default"])}))):u.type&&-1!==u.type.indexOf("array")?(o=[],i.options&&i.options.setSchemaDefaults===!1||(o=u["default"]||o)):i.options&&i.options.setSchemaDefaults===!1||(o=u["default"]||o)}return l.push(o),l},i.deleteFromArray=function(e){var t=i.modelArray;return t&&t.splice(e,1),t};var c=function(e){return function(t){t.key&&(t.key[t.key.indexOf("")]=e)}},f={};i.copyWithIndex=function(t){var r=i.form;if(!f[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,c(t)),f[t]=a}}return f[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,m,d){s.formCtrl=m;var p={};d(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 m={},d=c[0].querySelectorAll("*[sf-insert-field]"),p=0;p0&&(y.schema=e,y.form=t,v(e,t))}),s.$on("schemaFormRedraw",function(){var e=s.schema,t=s.initialForm||["*"];e&&v(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)},r.$on("schemaFormValidate",r.validateField),r.schemaError=function(){return l}}}}]),o}); \ No newline at end of file diff --git a/src/directives/schema-form.js b/src/directives/schema-form.js index a6d828a71..79603ded6 100644 --- a/src/directives/schema-form.js +++ b/src/directives/schema-form.js @@ -5,8 +5,8 @@ FIXME: real documentation angular.module('schemaForm') .directive('sfSchema', -['$compile', 'schemaForm', 'schemaFormDecorators', 'sfSelect', 'sfPath', 'sfBuilder', - function($compile, schemaForm, schemaFormDecorators, sfSelect, sfPath, sfBuilder) { +['$compile', '$http', '$templateCache', '$q','schemaForm', 'schemaFormDecorators', 'sfSelect', 'sfPath', 'sfBuilder', + function($compile, $http, $templateCache, $q, schemaForm, schemaFormDecorators, sfSelect, sfPath, sfBuilder) { return { scope: { @@ -65,8 +65,27 @@ angular.module('schemaForm') // Common renderer function, can either be triggered by a watch or by an event. var render = function(schema, form) { - var merged = schemaForm.merge(schema, form, ignore, scope.options); + var asyncTemplates = []; + var merged = schemaForm.merge(schema, form, ignore, scope.options, undefined, asyncTemplates); + + if (asyncTemplates.length > 0) { + // Pre load all async templates and put them on the form for the builder to use. + $q.all(asyncTemplates.map(function(form) { + return $http.get(form.templateUrl, {cache: $templateCache}).then(function(res) { + form.template = res.data; + }); + })).then(function() { + internalRender(schema, form, merged); + }); + + } else { + internalRender(schema, form, merged); + } + + + }; + var internalRender = function(schema, form, merged) { // Create a new form and destroy the old one. // Not doing keeps old form elements hanging around after // they have been removed from the DOM diff --git a/src/services/builder.js b/src/services/builder.js index 0d4724d79..34387f999 100644 --- a/src/services/builder.js +++ b/src/services/builder.js @@ -209,7 +209,7 @@ angular.module('schemaForm').provider('sfBuilder', ['sfPathProvider', function(s // measure optmization. A good start is probably a cache of DOM nodes for a particular // template that can be cloned instead of using innerHTML var div = document.createElement('div'); - var template = templateFn(field.template) || templateFn([decorator['default'].template]); + var template = templateFn(f, field) || templateFn(f, decorator['default']); div.innerHTML = template; // Move node to a document fragment, we don't want the div. @@ -233,11 +233,14 @@ angular.module('schemaForm').provider('sfBuilder', ['sfPathProvider', function(s }; + // Let the form definiton override builders if it wants to. + var builderFn = f.builder || field.builder; + // Builders are either a function or a list of functions. - if (typeof field.builder === 'function') { - field.builder(args); + if (typeof builderFn === 'function') { + builderFn(args); } else { - field.builder.forEach(function(fn) { fn(args); }); + builderFn.forEach(function(fn) { fn(args); }); } // Append @@ -254,8 +257,11 @@ angular.module('schemaForm').provider('sfBuilder', ['sfPathProvider', function(s * Builds a form from a canonical form definition */ build: function(form, decorator, slots, lookup) { - return build(form, decorator, function(url) { - return $templateCache.get(url); + return build(form, decorator, function(form, field) { + if (form.type === 'template') { + return form.template; + } + return $templateCache.get(field.template); }, slots, undefined, undefined, lookup); }, diff --git a/src/services/schema-form.js b/src/services/schema-form.js index d4722057c..cfee49494 100644 --- a/src/services/schema-form.js +++ b/src/services/schema-form.js @@ -300,7 +300,7 @@ angular.module('schemaForm').provider('schemaForm', var service = {}; - service.merge = function(schema, form, ignore, options, readonly) { + service.merge = function(schema, form, ignore, options, readonly, asyncTemplates) { form = form || ['*']; options = options || {}; @@ -371,13 +371,13 @@ angular.module('schemaForm').provider('schemaForm', //if it's a type with items, merge 'em! if (obj.items) { - obj.items = service.merge(schema, obj.items, ignore, options, obj.readonly); + obj.items = service.merge(schema, obj.items, ignore, options, obj.readonly, asyncTemplates); } //if its has tabs, merge them also! if (obj.tabs) { angular.forEach(obj.tabs, function(tab) { - tab.items = service.merge(schema, tab.items, ignore, options, obj.readonly); + tab.items = service.merge(schema, tab.items, ignore, options, obj.readonly, asyncTemplates); }); } @@ -386,6 +386,13 @@ angular.module('schemaForm').provider('schemaForm', if (obj.type === 'checkbox' && angular.isUndefined(obj.schema['default'])) { obj.schema['default'] = false; } + + // Special case: template type with tempplateUrl that's needs to be loaded before rendering + // TODO: this is not a clean solution. Maybe something cleaner can be made when $ref support + // is introduced since we need to go async then anyway + if (asyncTemplates && obj.type === 'template' && !obj.template && obj.templateUrl) { + asyncTemplates.push(obj); + } return obj; })); From dab00248559e46d89809e7f33460e129ebc4216c Mon Sep 17 00:00:00 2001 From: David Jensen Date: Thu, 20 Aug 2015 16:06:37 +0200 Subject: [PATCH 008/106] version bump and CHANGELOG --- CHANGELOG | 12 +++++++++--- package.json | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1ef8af542..547f06818 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,11 +1,17 @@ +v0.8.8 +------ + * Don't rely on documentFragment.children @davidlgj + * Restored "template" type support with the builder. @davidlgj + * Fixed defaults in array items. @davidlgj + v0.8.7 ------ - * Moved common builder functions from angular-schema-form-bootstrap decorator. - * Bugfx for the new builder. + * Moved common builder functions from angular-schema-form-bootstrap decorator. @davidlgj + * Bugfx for the new builder. @davidlgj v0.8.6 ------ - * Removed left over console.timeEnd + * Removed left over console.timeEnd @davidlgj v0.8.5 ------ diff --git a/package.json b/package.json index cfd301321..c5e5faa11 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-schema-form", - "version": "0.8.7", + "version": "0.8.8", "description": "Create complex forms from a JSON schema with angular.", "repository": "Textalk/angular-schema-form", "main": "dist/schema-form.min.js", From 5aa1168823eca9424b95e6d02cbb82689858dcae Mon Sep 17 00:00:00 2001 From: David Jensen Date: Fri, 4 Sep 2015 13:19:29 +0200 Subject: [PATCH 009/106] Validate when model changes Also update error messages when model changes even if error state hasn't changed. Also introduces two global options Default values: pristine : {error: true, success: true}, // Should errors and success be visible regardless of $pristine? validateOnRender: false // Should form fields be validated on form render? --- docs/index.md | 16 +++--- src/directives/array.js | 20 ++++++- src/directives/field.js | 24 ++++++-- src/directives/message.js | 94 +++++++++++++++++++------------ src/directives/schema-validate.js | 21 ++++++- src/services/decorators.js | 21 ++++++- 6 files changed, 141 insertions(+), 55 deletions(-) diff --git a/docs/index.md b/docs/index.md index a01d1efce..a70f8e794 100644 --- a/docs/index.md +++ b/docs/index.md @@ -187,13 +187,15 @@ attribute which should be placed along side `sf-schema`. `sf-options` takes an object with the following possible attributes. -| Attribute | | -|:--------------|:------------------------| -| supressPropertyTitles | by default schema form uses the property name in the schema as a title if none is specified, set this to true to disable that behavior | -| formDefaults | an object that will be used as a default for all form definitions | -| validationMessage | an object or a function that will be used as default validation message for all fields. See [Validation Messages](#validation-messages) for details. | -| setSchemaDefaults | boolean, set to false an no defaults from the schema will be set on the model. | -| destroyStrategy | the default strategy to use for cleaning the model when a form element is removed. see [destroyStrategy](#destroyStrategy) below | +| Attribute | Type | | +|:--------------|:------|:-------------------| +| supressPropertyTitles | boolean |by default schema form uses the property name in the schema as a title if none is specified, set this to true to disable that behavior | +| formDefaults | object | an object that will be used as a default for all form definitions | +| validationMessage | object or function | Object or a function that will be used as default validation message for all fields. See [Validation Messages](#validation-messages) for details. | +| setSchemaDefaults | boolean | Should schema defaults be set on model. | +| destroyStrategy | string | the default strategy to use for cleaning the model when a form element is removed. see [destroyStrategy](#destroyStrategy) below | +| pristine | Object `{errors ,success}` | Sets if errors and success states should be visible when form field are `$pristine`. Default is `{errors: true, success: true}` | +| validateOnRender | boolean | Should form be validated on initial render? Default `false` | *formDefaults* is mostly useful for setting global [ngModelOptions](#ngmodeloptions) i.e. changing the entire form to validate on blur. diff --git a/src/directives/array.js b/src/directives/array.js index 90c0c9557..bd936d1d5 100644 --- a/src/directives/array.js +++ b/src/directives/array.js @@ -240,11 +240,27 @@ angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sf scope.$on('schemaFormValidate', scope.validateArray); scope.hasSuccess = function() { - return ngModel.$valid && !ngModel.$pristine; + if (scope.options && scope.options.pristine && + scope.options.pristine.success === false) { + return ngModel.$valid && + !ngModel.$pristine && !ngModel.$isEmpty(ngModel.$modelValue); + } else { + return ngModel.$valid && + (!ngModel.$pristine || !ngModel.$isEmpty(ngModel.$modelValue)); + } }; scope.hasError = function() { - return ngModel.$invalid; + if (!scope.options || !scope.options.pristine || scope.options.pristine.errors !== false) { + // Show errors in pristine forms. The default. + // Note that "validateOnRender" option defaults to *not* validate initial form. + // so as a default there won't be any error anyway, but if the model is modified + // from the outside the error will show even if the field is pristine. + return ngModel.$invalid; + } else { + // Don't show errors in pristine forms. + return ngModel.$invalid && !ngModel.$pristine; + } }; scope.schemaError = function() { diff --git a/src/directives/field.js b/src/directives/field.js index 41142b259..556f30c59 100644 --- a/src/directives/field.js +++ b/src/directives/field.js @@ -107,20 +107,35 @@ angular.module('schemaForm').directive('sfField', return (expression && $interpolate(expression)(locals)); }; - //This works since we ot the ngModel from the array or the schema-validate directive. + //This works since we get the ngModel from the array or the schema-validate directive. scope.hasSuccess = function() { if (!scope.ngModel) { return false; } - return scope.ngModel.$valid && + if (scope.options && scope.options.pristine && + scope.options.pristine.success === false) { + return scope.ngModel.$valid && + !scope.ngModel.$pristine && !scope.ngModel.$isEmpty(scope.ngModel.$modelValue); + } else { + return scope.ngModel.$valid && (!scope.ngModel.$pristine || !scope.ngModel.$isEmpty(scope.ngModel.$modelValue)); + } }; scope.hasError = function() { if (!scope.ngModel) { return false; } - return scope.ngModel.$invalid && !scope.ngModel.$pristine; + if (!scope.options || !scope.options.pristine || scope.options.pristine.errors !== false) { + // Show errors in pristine forms. The default. + // Note that "validateOnRender" option defaults to *not* validate initial form. + // so as a default there won't be any error anyway, but if the model is modified + // from the outside the error will show even if the field is pristine. + return scope.ngModel.$invalid; + } else { + // Don't show errors in pristine forms. + return scope.ngModel.$invalid && !scope.ngModel.$pristine; + } }; /** @@ -178,7 +193,8 @@ angular.module('schemaForm').directive('sfField', scope.$broadcast('schemaFormValidate'); } } - }); + } + ); // Clean up the model when the corresponding form field is $destroy-ed. // Default behavior can be supplied as a globalOption, and behavior can be overridden diff --git a/src/directives/message.js b/src/directives/message.js index 2a723f650..50843d467 100644 --- a/src/directives/message.js +++ b/src/directives/message.js @@ -15,60 +15,80 @@ angular.module('schemaForm').directive('sfMessage', scope.$watch(attrs.sfMessage, function(msg) { if (msg) { message = $sanitize(msg); - if (scope.ngModel) { - update(scope.ngModel.$valid); - } else { - update(); - } + update(!!scope.ngModel); } }); } - var update = function(valid) { - if (valid && !scope.hasError()) { - element.html(message); - } else { - var errors = []; - angular.forEach(((scope.ngModel && scope.ngModel.$error) || {}), function(status, code) { - if (status) { - // if true then there is an error - // Angular 1.3 removes properties, so we will always just have errors. - // Angular 1.2 sets them to false. - errors.push(code); - } - }); + var currentMessage; + // Only call html() if needed. + var setMessage = function(msg) { + if (msg !== currentMessage) { + element.html(msg); + currentMessage = msg; + } + }; - // In Angular 1.3 we use one $validator to stop the model value from getting updated. - // this means that we always end up with a 'schemaForm' error. - errors = errors.filter(function(e) { return e !== 'schemaForm'; }); + var update = function(checkForErrors) { + if (checkForErrors) { + if (!scope.hasError()) { + setMessage(message); + } else { + var errors = []; + angular.forEach(scope.ngModel && scope.ngModel.$error, function(status, code) { + if (status) { + // if true then there is an error + // Angular 1.3 removes properties, so we will always just have errors. + // Angular 1.2 sets them to false. + errors.push(code); + } + }); - // We only show one error. - // TODO: Make that optional - var error = errors[0]; + // In Angular 1.3 we use one $validator to stop the model value from getting updated. + // this means that we always end up with a 'schemaForm' error. + errors = errors.filter(function(e) { return e !== 'schemaForm'; }); - if (error) { - element.html(sfErrorMessage.interpolate( - error, - scope.ngModel.$modelValue, - scope.ngModel.$viewValue, - scope.form, - scope.options && scope.options.validationMessage - )); - } else { - element.html(message); + // We only show one error. + // TODO: Make that optional + var error = errors[0]; + + if (error) { + setMessage(sfErrorMessage.interpolate( + error, + scope.ngModel.$modelValue, + scope.ngModel.$viewValue, + scope.form, + scope.options && scope.options.validationMessage + )); + } else { + setMessage(message); + } } + } else { + setMessage(message); } }; // Update once. update(); - scope.$watchCollection('ngModel.$error', function() { - if (scope.ngModel) { - update(scope.ngModel.$valid); + var once = scope.$watch('ngModel',function(ngModel) { + if (ngModel) { + // We also listen to changes of the model via parsers and formatters. + // This is since both the error message can change and given a pristine + // option to not show errors the ngModel.$error might not have changed + // but we're not pristine any more so we should change! + ngModel.$parsers.push(function(val) { update(true); return val; }); + ngModel.$formatters.push(function(val) { update(true); return val; }); + once(); } }); + // We watch for changes in $error + scope.$watchCollection('ngModel.$error', function() { + update(!!scope.ngModel); + }); + } }; }]); diff --git a/src/directives/schema-validate.js b/src/directives/schema-validate.js index 19e46710f..eedcdcc24 100644 --- a/src/directives/schema-validate.js +++ b/src/directives/schema-validate.js @@ -24,11 +24,13 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', '$parse sfSelect(path, scope.model, ngModel.$modelValue); }); }); - } + }; + // Validate against the schema. var validate = function(viewValue) { + //console.log('validate called', viewValue) //Still might be undefined if (!form) { return viewValue; @@ -40,7 +42,7 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', '$parse } var result = sfValidator.validate(form, viewValue); - + //console.log('result is', result) // Since we might have different tv4 errors we must clear all // errors that start with tv4- Object.keys(ngModel.$error) @@ -95,6 +97,7 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', '$parse // updating if we've found an error. if (ngModel.$validators) { ngModel.$validators.schemaForm = function() { + //console.log('validators called.') // Any error and we're out of here! return !Object.keys(ngModel.$error).some(function(e) { return e !== 'schemaForm';}); }; @@ -138,6 +141,20 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', '$parse } }; + var first = true; + ngModel.$formatters.push(function(val) { + + // When a form first loads this will be called for each field. + // we usually don't want that. + if (ngModel.$pristine && first && + (!scope.options || scope.options.validateOnRender !== true)) { + first = false; + return val; + } + validate(ngModel.$modelValue); + return val; + }); + // Listen to an event so we can validate the input on request scope.$on('schemaFormValidate', scope.validateField); diff --git a/src/services/decorators.js b/src/services/decorators.js index f7172d909..40266139f 100644 --- a/src/services/decorators.js +++ b/src/services/decorators.js @@ -125,20 +125,35 @@ angular.module('schemaForm').provider('schemaFormDecorators', return (expression && $interpolate(expression)(locals)); }; - //This works since we ot the ngModel from the array or the schema-validate directive. + //This works since we get the ngModel from the array or the schema-validate directive. scope.hasSuccess = function() { if (!scope.ngModel) { return false; } - return scope.ngModel.$valid && + if (scope.options && scope.options.pristine && + scope.options.pristine.success === false) { + return scope.ngModel.$valid && + !scope.ngModel.$pristine && !scope.ngModel.$isEmpty(scope.ngModel.$modelValue); + } else { + return scope.ngModel.$valid && (!scope.ngModel.$pristine || !scope.ngModel.$isEmpty(scope.ngModel.$modelValue)); + } }; scope.hasError = function() { if (!scope.ngModel) { return false; } - return scope.ngModel.$invalid && !scope.ngModel.$pristine; + if (!scope.options || !scope.options.pristine || scope.options.pristine.errors !== false) { + // Show errors in pristine forms. The default. + // Note that "validateOnRender" option defaults to *not* validate initial form. + // so as a default there won't be any error anyway, but if the model is modified + // from the outside the error will show even if the field is pristine. + return scope.ngModel.$invalid; + } else { + // Don't show errors in pristine forms. + return scope.ngModel.$invalid && !scope.ngModel.$pristine; + } }; /** From 61f584e3b8114688588480af9a9a3360ad5fcced Mon Sep 17 00:00:00 2001 From: David Jensen Date: Fri, 4 Sep 2015 13:27:10 +0200 Subject: [PATCH 010/106] gulped --- dist/schema-form.js | 182 +++++++++++++++++++++++++++++----------- dist/schema-form.min.js | 2 +- 2 files changed, 134 insertions(+), 50 deletions(-) diff --git a/dist/schema-form.js b/dist/schema-form.js index 0796a752f..0d7fdeaae 100644 --- a/dist/schema-form.js +++ b/dist/schema-form.js @@ -541,20 +541,35 @@ angular.module('schemaForm').provider('schemaFormDecorators', return (expression && $interpolate(expression)(locals)); }; - //This works since we ot the ngModel from the array or the schema-validate directive. + //This works since we get the ngModel from the array or the schema-validate directive. scope.hasSuccess = function() { if (!scope.ngModel) { return false; } - return scope.ngModel.$valid && + if (scope.options && scope.options.pristine && + scope.options.pristine.success === false) { + return scope.ngModel.$valid && + !scope.ngModel.$pristine && !scope.ngModel.$isEmpty(scope.ngModel.$modelValue); + } else { + return scope.ngModel.$valid && (!scope.ngModel.$pristine || !scope.ngModel.$isEmpty(scope.ngModel.$modelValue)); + } }; scope.hasError = function() { if (!scope.ngModel) { return false; } - return scope.ngModel.$invalid && !scope.ngModel.$pristine; + if (!scope.options || !scope.options.pristine || scope.options.pristine.errors !== false) { + // Show errors in pristine forms. The default. + // Note that "validateOnRender" option defaults to *not* validate initial form. + // so as a default there won't be any error anyway, but if the model is modified + // from the outside the error will show even if the field is pristine. + return scope.ngModel.$invalid; + } else { + // Don't show errors in pristine forms. + return scope.ngModel.$invalid && !scope.ngModel.$pristine; + } }; /** @@ -1809,11 +1824,27 @@ angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sf scope.$on('schemaFormValidate', scope.validateArray); scope.hasSuccess = function() { - return ngModel.$valid && !ngModel.$pristine; + if (scope.options && scope.options.pristine && + scope.options.pristine.success === false) { + return ngModel.$valid && + !ngModel.$pristine && !ngModel.$isEmpty(ngModel.$modelValue); + } else { + return ngModel.$valid && + (!ngModel.$pristine || !ngModel.$isEmpty(ngModel.$modelValue)); + } }; scope.hasError = function() { - return ngModel.$invalid; + if (!scope.options || !scope.options.pristine || scope.options.pristine.errors !== false) { + // Show errors in pristine forms. The default. + // Note that "validateOnRender" option defaults to *not* validate initial form. + // so as a default there won't be any error anyway, but if the model is modified + // from the outside the error will show even if the field is pristine. + return ngModel.$invalid; + } else { + // Don't show errors in pristine forms. + return ngModel.$invalid && !ngModel.$pristine; + } }; scope.schemaError = function() { @@ -1967,20 +1998,35 @@ angular.module('schemaForm').directive('sfField', return (expression && $interpolate(expression)(locals)); }; - //This works since we ot the ngModel from the array or the schema-validate directive. + //This works since we get the ngModel from the array or the schema-validate directive. scope.hasSuccess = function() { if (!scope.ngModel) { return false; } - return scope.ngModel.$valid && + if (scope.options && scope.options.pristine && + scope.options.pristine.success === false) { + return scope.ngModel.$valid && + !scope.ngModel.$pristine && !scope.ngModel.$isEmpty(scope.ngModel.$modelValue); + } else { + return scope.ngModel.$valid && (!scope.ngModel.$pristine || !scope.ngModel.$isEmpty(scope.ngModel.$modelValue)); + } }; scope.hasError = function() { if (!scope.ngModel) { return false; } - return scope.ngModel.$invalid && !scope.ngModel.$pristine; + if (!scope.options || !scope.options.pristine || scope.options.pristine.errors !== false) { + // Show errors in pristine forms. The default. + // Note that "validateOnRender" option defaults to *not* validate initial form. + // so as a default there won't be any error anyway, but if the model is modified + // from the outside the error will show even if the field is pristine. + return scope.ngModel.$invalid; + } else { + // Don't show errors in pristine forms. + return scope.ngModel.$invalid && !scope.ngModel.$pristine; + } }; /** @@ -2038,7 +2084,8 @@ angular.module('schemaForm').directive('sfField', scope.$broadcast('schemaFormValidate'); } } - }); + } + ); // Clean up the model when the corresponding form field is $destroy-ed. // Default behavior can be supplied as a globalOption, and behavior can be overridden @@ -2105,60 +2152,80 @@ angular.module('schemaForm').directive('sfMessage', scope.$watch(attrs.sfMessage, function(msg) { if (msg) { message = $sanitize(msg); - if (scope.ngModel) { - update(scope.ngModel.$valid); - } else { - update(); - } + update(!!scope.ngModel); } }); } - var update = function(valid) { - if (valid && !scope.hasError()) { - element.html(message); - } else { - var errors = []; - angular.forEach(((scope.ngModel && scope.ngModel.$error) || {}), function(status, code) { - if (status) { - // if true then there is an error - // Angular 1.3 removes properties, so we will always just have errors. - // Angular 1.2 sets them to false. - errors.push(code); - } - }); + var currentMessage; + // Only call html() if needed. + var setMessage = function(msg) { + if (msg !== currentMessage) { + element.html(msg); + currentMessage = msg; + } + }; - // In Angular 1.3 we use one $validator to stop the model value from getting updated. - // this means that we always end up with a 'schemaForm' error. - errors = errors.filter(function(e) { return e !== 'schemaForm'; }); - - // We only show one error. - // TODO: Make that optional - var error = errors[0]; - - if (error) { - element.html(sfErrorMessage.interpolate( - error, - scope.ngModel.$modelValue, - scope.ngModel.$viewValue, - scope.form, - scope.options && scope.options.validationMessage - )); + var update = function(checkForErrors) { + if (checkForErrors) { + if (!scope.hasError()) { + setMessage(message); } else { - element.html(message); + var errors = []; + angular.forEach(scope.ngModel && scope.ngModel.$error, function(status, code) { + if (status) { + // if true then there is an error + // Angular 1.3 removes properties, so we will always just have errors. + // Angular 1.2 sets them to false. + errors.push(code); + } + }); + + // In Angular 1.3 we use one $validator to stop the model value from getting updated. + // this means that we always end up with a 'schemaForm' error. + errors = errors.filter(function(e) { return e !== 'schemaForm'; }); + + // We only show one error. + // TODO: Make that optional + var error = errors[0]; + + if (error) { + setMessage(sfErrorMessage.interpolate( + error, + scope.ngModel.$modelValue, + scope.ngModel.$viewValue, + scope.form, + scope.options && scope.options.validationMessage + )); + } else { + setMessage(message); + } } + } else { + setMessage(message); } }; // Update once. update(); - scope.$watchCollection('ngModel.$error', function() { - if (scope.ngModel) { - update(scope.ngModel.$valid); + var once = scope.$watch('ngModel',function(ngModel) { + if (ngModel) { + // We also listen to changes of the model via parsers and formatters. + // This is since both the error message can change and given a pristine + // option to not show errors the ngModel.$error might not have changed + // but we're not pristine any more so we should change! + ngModel.$parsers.push(function(val) { update(true); return val; }); + ngModel.$formatters.push(function(val) { update(true); return val; }); + once(); } }); + // We watch for changes in $error + scope.$watchCollection('ngModel.$error', function() { + update(!!scope.ngModel); + }); + } }; }]); @@ -2619,11 +2686,13 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', '$parse sfSelect(path, scope.model, ngModel.$modelValue); }); }); - } + }; + // Validate against the schema. var validate = function(viewValue) { + //console.log('validate called', viewValue) //Still might be undefined if (!form) { return viewValue; @@ -2635,7 +2704,7 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', '$parse } var result = sfValidator.validate(form, viewValue); - + //console.log('result is', result) // Since we might have different tv4 errors we must clear all // errors that start with tv4- Object.keys(ngModel.$error) @@ -2690,6 +2759,7 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', '$parse // updating if we've found an error. if (ngModel.$validators) { ngModel.$validators.schemaForm = function() { + //console.log('validators called.') // Any error and we're out of here! return !Object.keys(ngModel.$error).some(function(e) { return e !== 'schemaForm';}); }; @@ -2733,6 +2803,20 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', '$parse } }; + var first = true; + ngModel.$formatters.push(function(val) { + + // When a form first loads this will be called for each field. + // we usually don't want that. + if (ngModel.$pristine && first && + (!scope.options || scope.options.validateOnRender !== true)) { + first = false; + return val; + } + validate(ngModel.$modelValue); + return val; + }); + // Listen to an event so we can validate the input on request scope.$on('schemaFormValidate', scope.validateField); diff --git a/dist/schema-form.min.js b/dist/schema-form.min.js index a56e0cfa4..9ce03a636 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;)d.appendChild(p.childNodes[0]);var y={fieldFrag:d,form:c,lookup:u,state:s,path:l+"["+f+"]",build:function(e,n,i){return a(e,t,r,o,n,i,u)}},v=c.builder||m.builder;"function"==typeof v?v(y):v.forEach(function(e){e(y)}),(i(c,o)||e).appendChild(d)}else{var g=document.createElement(n(t.__name,"-"));s.arrayCompatFlag?g.setAttribute("form","copyWithIndex($index)"):g.setAttribute("form",l+"["+f+"]"),(i(c,o)||e).appendChild(g)}return e}},c),c};return{build:function(t,r,n,i){return a(t,r,function(t,r){return"template"===t.type?t.template:e.get(r.template)},n,void 0,void 0,i)},builder:o,internalBuild:a}}]}]),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,m){return{restrict:"AE",replace:!1,transclude:!1,scope:!0,require:"?^sfSchema",link:function(t,d,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 y=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(d.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(d.children(),function(e){var t=e.getAttribute("ng-if");e.setAttribute("ng-if",t?"("+t+") || ("+a+")":a)})}i(d.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=m(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)]}}})),y()}})}}}])},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.$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&&(d={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]&&d){var n=e.copy(d);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 u.$valid&&!u.$pristine},a.hasError=function(){return 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.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 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),t.ngModel?l(t.ngModel.$valid):l())});var l=function(n){if(n&&!t.hasError())i.html(a);else{var o=[];e.forEach(t.ngModel&&t.ngModel.$error||{},function(e,t){e&&o.push(t)}),o=o.filter(function(e){return"schemaForm"!==e});var l=o[0];l?i.html(r.interpolate(l,t.ngModel.$modelValue,t.ngModel.$viewValue,t.form,t.options&&t.options.validationMessage)):i.html(a)}};l(),t.$watchCollection("ngModel.$error",function(){t.ngModel&&l(t.ngModel.$valid)})}}}]),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.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=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=i.modelArray;n.length>0;)n.pop();e.titleMap.forEach(function(e,r){t[r]&&n.push(e.value)}),i.validateField&&i.validateField()}})}u()}});i.appendToArray=function(){var o,l=i.modelArray;if(!l){var s=r.parse(a.sfNewArray);l=[],t(s,i,l),i.modelArray=l}if(i.form&&i.form.schema&&i.form.schema.items){var u=i.form.schema.items;u.type&&-1!==u.type.indexOf("object")?(o={},i.options&&i.options.setSchemaDefaults===!1||(o=e.isDefined(u["default"])?u["default"]:o,o&&n.traverseSchema(u,function(r,n){e.isDefined(r["default"])&&t(n,o,r["default"])}))):u.type&&-1!==u.type.indexOf("array")?(o=[],i.options&&i.options.setSchemaDefaults===!1||(o=u["default"]||o)):i.options&&i.options.setSchemaDefaults===!1||(o=u["default"]||o)}return l.push(o),l},i.deleteFromArray=function(e){var t=i.modelArray;return t&&t.splice(e,1),t};var c=function(e){return function(t){t.key&&(t.key[t.key.indexOf("")]=e)}},f={};i.copyWithIndex=function(t){var r=i.form;if(!f[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,c(t)),f[t]=a}}return f[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,m,d){s.formCtrl=m;var p={};d(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 m={},d=c[0].querySelectorAll("*[sf-insert-field]"),p=0;p0&&(y.schema=e,y.form=t,v(e,t))}),s.$on("schemaFormRedraw",function(){var e=s.schema,t=s.initialForm||["*"];e&&v(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)},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").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;)d.appendChild(p.childNodes[0]);var y={fieldFrag:d,form:c,lookup:u,state:s,path:l+"["+f+"]",build:function(e,n,i){return a(e,t,r,o,n,i,u)}},v=c.builder||m.builder;"function"==typeof v?v(y):v.forEach(function(e){e(y)}),(i(c,o)||e).appendChild(d)}else{var g=document.createElement(n(t.__name,"-"));s.arrayCompatFlag?g.setAttribute("form","copyWithIndex($index)"):g.setAttribute("form",l+"["+f+"]"),(i(c,o)||e).appendChild(g)}return e}},c),c};return{build:function(t,r,n,i){return a(t,r,function(t,r){return"template"===t.type?t.template:e.get(r.template)},n,void 0,void 0,i)},builder:o,internalBuild:a}}]}]),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,m){return{restrict:"AE",replace:!1,transclude:!1,scope:!0,require:"?^sfSchema",link:function(t,d,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.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 c.interpolate(e&&e.code+""||"default",t.ngModel&&t.ngModel.$modelValue||"",t.ngModel&&t.ngModel.$viewValue||"",t.form,t.options&&t.options.validationMessage)};var y=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(d.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(d.children(),function(e){var t=e.getAttribute("ng-if");e.setAttribute("ng-if",t?"("+t+") || ("+a+")":a)})}i(d.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=m(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)]}}})),y()}})}}}])},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.$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&&(d={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]&&d){var n=e.copy(d);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.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=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=i.modelArray;n.length>0;)n.pop();e.titleMap.forEach(function(e,r){t[r]&&n.push(e.value)}),i.validateField&&i.validateField()}})}u()}});i.appendToArray=function(){var o,l=i.modelArray;if(!l){var s=r.parse(a.sfNewArray);l=[],t(s,i,l),i.modelArray=l}if(i.form&&i.form.schema&&i.form.schema.items){var u=i.form.schema.items;u.type&&-1!==u.type.indexOf("object")?(o={},i.options&&i.options.setSchemaDefaults===!1||(o=e.isDefined(u["default"])?u["default"]:o,o&&n.traverseSchema(u,function(r,n){e.isDefined(r["default"])&&t(n,o,r["default"])}))):u.type&&-1!==u.type.indexOf("array")?(o=[],i.options&&i.options.setSchemaDefaults===!1||(o=u["default"]||o)):i.options&&i.options.setSchemaDefaults===!1||(o=u["default"]||o)}return l.push(o),l},i.deleteFromArray=function(e){var t=i.modelArray;return t&&t.splice(e,1),t};var c=function(e){return function(t){t.key&&(t.key[t.key.indexOf("")]=e)}},f={};i.copyWithIndex=function(t){var r=i.form;if(!f[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,c(t)),f[t]=a}}return f[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,m,d){s.formCtrl=m;var p={};d(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 m={},d=c[0].querySelectorAll("*[sf-insert-field]"),p=0;p0&&(y.schema=e,y.form=t,v(e,t))}),s.$on("schemaFormRedraw",function(){var e=s.schema,t=s.initialForm||["*"];e&&v(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)};var f=!0;a.$formatters.push(function(e){return!a.$pristine||!f||r.options&&r.options.validateOnRender===!0?(u(a.$modelValue),e):(f=!1,e)}),r.$on("schemaFormValidate",r.validateField),r.schemaError=function(){return l}}}}]),o}); \ No newline at end of file From 21db7fd6591de913276ed1a8114dffe66ec32425 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Fri, 4 Sep 2015 13:43:30 +0200 Subject: [PATCH 011/106] Fix for #430 Radios validation failing --- src/directives/decorators/bootstrap/radios.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/directives/decorators/bootstrap/radios.html b/src/directives/decorators/bootstrap/radios.html index f3b73189b..10f8d61b4 100644 --- a/src/directives/decorators/bootstrap/radios.html +++ b/src/directives/decorators/bootstrap/radios.html @@ -1,5 +1,8 @@
- +