From e975b50c49cbe8b11262d372d7855e9672232995 Mon Sep 17 00:00:00 2001 From: luciformed Date: Wed, 29 Apr 2015 16:54:48 +0100 Subject: [PATCH 1/8] call $setViewValue, when items are added or removed from array --- dist/bootstrap-decorator.js | 2 +- dist/bootstrap-decorator.min.js | 2 +- dist/schema-form.js | 28 ++++++++++++++----- dist/schema-form.min.js | 2 +- examples/data/array.json | 1 + src/directives/array.js | 12 ++++++++ .../decorators/bootstrap/array.html | 2 +- 7 files changed, 38 insertions(+), 11 deletions(-) diff --git a/dist/bootstrap-decorator.js b/dist/bootstrap-decorator.js index 6c8a24edb..1fc97dc61 100644 --- a/dist/bootstrap-decorator.js +++ b/dist/bootstrap-decorator.js @@ -1,6 +1,6 @@ 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/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)\' }}
"); diff --git a/dist/bootstrap-decorator.min.js b/dist/bootstrap-decorator.min.js index 459e65a16..640b90a95 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.createDecorator("bootstrapDecorator",{textarea:t+"textarea.html",fieldset:t+"fieldset.html",array:t+"array.html",tabarray:t+"tabarray.html",tabs:t+"tabs.html",section:t+"section.html",conditional:t+"section.html",actions:t+"actions.html",select:t+"select.html",checkbox:t+"checkbox.html",checkboxes:t+"checkboxes.html",number:t+"default.html",password:t+"default.html",submit:t+"submit.html",button:t+"submit.html",radios:t+"radios.html","radios-inline":t+"radios-inline.html",radiobuttons:t+"radio-buttons.html",help:t+"help.html","default":t+"default.html"},[]),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.createDecorator("bootstrapDecorator",{textarea:t+"textarea.html",fieldset:t+"fieldset.html",array:t+"array.html",tabarray:t+"tabarray.html",tabs:t+"tabs.html",section:t+"section.html",conditional:t+"section.html",actions:t+"actions.html",select:t+"select.html",checkbox:t+"checkbox.html",checkboxes:t+"checkboxes.html",number:t+"default.html",password:t+"default.html",submit:t+"submit.html",button:t+"submit.html",radios:t+"radios.html","radios-inline":t+"radios-inline.html",radiobuttons:t+"radio-buttons.html",help:t+"help.html","default":t+"default.html"},[]),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 diff --git a/dist/schema-form.js b/dist/schema-form.js index 711f5fcdd..3b587a12c 100644 --- a/dist/schema-form.js +++ b/dist/schema-form.js @@ -1345,6 +1345,9 @@ angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sf if (scope.validateArray) { scope.validateArray(); } + + setViewValue(list); + return list; }; @@ -1360,6 +1363,9 @@ angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sf if (ngModel && ngModel.$setDirty) { ngModel.$setDirty(); } + + setViewValue(list); + return list; }; @@ -1469,6 +1475,12 @@ angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sf once(); }); + + function setViewValue(value) { + if (ngModel) { + ngModel.$setViewValue(angular.copy(value)); + } + } } }; } @@ -1680,14 +1692,16 @@ angular.module('schemaForm') $compile(element.children())(childScope); //ok, now that that is done let's set any defaults - schemaForm.traverseSchema(schema, function(prop, path) { - if (angular.isDefined(prop['default'])) { - var val = sfSelect(path, scope.model); - if (angular.isUndefined(val)) { - sfSelect(path, scope.model, prop['default']); + if (!scope.options || scope.options.setSchemaDefaults !== false) { + schemaForm.traverseSchema(schema, function(prop, path) { + if (angular.isDefined(prop['default'])) { + var val = sfSelect(path, scope.model); + if (angular.isUndefined(val)) { + sfSelect(path, scope.model, prop['default']); + } } - } - }); + }); + } scope.$emit('sf-render-finished', element); }; diff --git a/dist/schema-form.min.js b/dist/schema-form.min.js index 47070d3da..a6a9db394 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={parse:t.parse};r.stringify=1===e.version.major&&e.version.minor<3?function(e){return Array.isArray(e)?e.join("."):e.toString()}:t.stringify,r.normalize=function(e,t){return r.stringify(Array.isArray(e)?e:r.parse(e),t)},this.parse=r.parse,this.stringify=r.stringify,this.normalize=r.normalize,this.$get=function(){return r}}]),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]],s=1;s',link:function(t,n,i){var o={items:"c",titleMap:"c",schema:"c"},a={type:r},s=!0;e.forEach(i,function(r,n){if("$"!==n[0]&&0!==n.indexOf("ng")&&"sfField"!==n){var l=function(r){e.isDefined(r)&&r!==a[n]&&(a[n]=r,s&&a.type&&(a.key||e.isUndefined(i.key))&&(t.form=a,s=!1))};"model"===n?t.$watch(r,function(e){e&&t.model!==e&&(t.model=e)}):"c"===o[n]?t.$watchCollection(r,l):i.$observe(n,l)}})}}})};this.createDecorator=function(e,t,r){i[e]={mappings:t||{},rules:r||[]},i[n]||(n=e),a(e)},this.createDirective=s,this.createDirectives=function(t){e.forEach(t,function(e,t){s(t,e)})},this.directive=function(e){return e=e||n,i[e]},this.addMapping=function(e,t,r){i[e]&&(i[e].mappings[t]=r)},this.$get=function(){return{directive:function(e){return i[e]},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: {{value}}",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.divisibleBy}}",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.maxItems}}",401:"Array is too long ({{value.length}}), maximum {{schema.minItems}}",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,s){s=s||{};var l=a.validationMessage||{};0===n.indexOf("tv4-")&&(n=n.substring(4));var u=l["default"]||s["default"]||"";[l,s,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){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=p[r(n.type)];if(o)for(var a,s=0;s1&&(m={type:"section",items:s.items.map(function(t){return t.ngModelOptions=s.ngModelOptions,e.isUndefined(t.readonly)&&(t.readonly=s.readonly),t})})}if(a.copyWithIndex=function(t){if(!c[t]&&m){var n=e.copy(m);n.arrayIndex=t,r.traverseForm(n,o(t)),c[t]=n}return c[t]},a.appendToArray=function(){var n=l.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===l.length){var o,u=t("schema.items.type",s);"object"===u?o={}:"array"===u&&(o=[]),l.push(o)}return a.validateArray&&a.validateArray(),l},a.deleteFromArray=function(e){return l.splice(e,1),a.validateArray&&a.validateArray(),u&&u.$setDirty&&u.$setDirty(),l},s.titleMap||s.startEmpty===!0||0!==l.length||a.appendToArray(),s.titleMap&&s.titleMap.length>0){a.titleMapValues=[];var d=function(e){a.titleMapValues=[],e=e||[],s.titleMap.forEach(function(t){a.titleMapValues.push(-1!==e.indexOf(t.value))})};d(a.modelArray),a.$watchCollection("modelArray",d),a.$watchCollection("titleMapValues",function(e){if(e){for(var t=a.modelArray;t.length>0;)t.pop();s.titleMap.forEach(function(r,n){e[n]&&t.push(r.value)})}})}if(u){var h;a.validateArray=function(){var e=n.validate(s,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!=="/"+s.key[s.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("sfMessage",["$injector","sfErrorMessage",function(e,t){return{scope:!1,restrict:"EA",link:function(r,n,i){var o=e.has("$sanitize")?e.get("$sanitize"):function(e){return e},a="";i.sfMessage&&(a=r.$eval(i.sfMessage)||"",a=o(a));var s=function(e){if(e&&!r.hasError())n.html(a);else{var i=Object.keys(r.ngModel&&r.ngModel.$error||{}),o=i[0];n.html(o?t.interpolate(o,r.ngModel.$modelValue,r.ngModel.$viewValue,r.form,r.options&&r.options.validationMessage):a)}};s(),r.$watchCollection("ngModel.$error",function(){r.ngModel&&s(r.ngModel.$valid)})}}}]),e.module("schemaForm").directive("sfSchema",["$compile","schemaForm","schemaFormDecorators","sfSelect","sfPath",function(t,r,n,i,o){var a=/[A-Z]/g,s=function(e,t){return t=t||"_",e.replace(a,function(e,r){return(r?t:"")+e.toLowerCase()})};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)}}],replace:!1,restrict:"A",transclude:!0,require:"?form",link:function(a,l,u,c,f){a.formCtrl=c;var m={};f(a,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&&(h.schema=e,h.form=t,p(e,t))}),a.$on("schemaFormRedraw",function(){var e=a.schema,t=a.initialForm||["*"];e&&p(e,t)})}}}]),e.module("schemaForm").directive("schemaValidate",["sfValidator","sfSelect",function(t,r){return{restrict:"A",scope:!1,priority:500,require:"ngModel",link:function(n,i,o,a){n.$emit("schemaFormPropagateNgModelController",a);var s=null,l=function(){return u||(u=n.$eval(o.schemaValidate)),u},u=l();u.copyValueTo&&a.$viewChangeListeners.push(function(){var t=u.copyValueTo;e.forEach(t,function(e){r(e,n.model,a.$modelValue)})});var c=function(e){if(u=l(),!u)return e;if(n.options&&n.options.tv4Validation===!1)return e;var r=t.validate(u,e);return Object.keys(a.$error).filter(function(e){return 0===e.indexOf("tv4-")}).forEach(function(e){a.$setValidity(e,!0)}),r.valid?e:(a.$setValidity("tv4-"+r.error.code,!1),void(s=r.error))};"function"==typeof u.ngModel&&u.ngModel(a),["$parsers","$viewChangeListeners","$formatters"].forEach(function(e){u[e]&&a[e]&&u[e].forEach(function(t){a[e].push(t)})}),["$validators","$asyncValidators"].forEach(function(t){u[t]&&a[t]&&e.forEach(u[t],function(e,r){a[t][r]=e})}),a.$parsers.push(c),n.$on("schemaFormValidate",function(){a.$setDirty?(a.$setDirty(),c(a.$modelValue)):a.$setViewValue(a.$viewValue)}),n.schemaError=function(){return s}}}}]),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={parse:t.parse};1===e.version.major&&e.version.minor<3?r.stringify=function(e){return Array.isArray(e)?e.join("."):e.toString()}:r.stringify=t.stringify,r.normalize=function(e,t){return r.stringify(Array.isArray(e)?e:r.parse(e),t)},this.parse=r.parse,this.stringify=r.stringify,this.normalize=r.normalize,this.$get=function(){return r}}]),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]],s=1;s',link:function(t,n,i){var o={items:"c",titleMap:"c",schema:"c"},a={type:r},s=!0;e.forEach(i,function(r,n){if("$"!==n[0]&&0!==n.indexOf("ng")&&"sfField"!==n){var l=function(r){e.isDefined(r)&&r!==a[n]&&(a[n]=r,s&&a.type&&(a.key||e.isUndefined(i.key))&&(t.form=a,s=!1))};"model"===n?t.$watch(r,function(e){e&&t.model!==e&&(t.model=e)}):"c"===o[n]?t.$watchCollection(r,l):i.$observe(n,l)}})}}})};this.createDecorator=function(e,t,r){i[e]={mappings:t||{},rules:r||[]},i[n]||(n=e),a(e)},this.createDirective=s,this.createDirectives=function(t){e.forEach(t,function(e,t){s(t,e)})},this.directive=function(e){return e=e||n,i[e]},this.addMapping=function(e,t,r){i[e]&&(i[e].mappings[t]=r)},this.$get=function(){return{directive:function(e){return i[e]},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: {{value}}",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.divisibleBy}}",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.maxItems}}",401:"Array is too long ({{value.length}}), maximum {{schema.minItems}}",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,s){s=s||{};var l=a.validationMessage||{};0===n.indexOf("tv4-")&&(n=n.substring(4));var u=l["default"]||s["default"]||"";[l,s,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=p[r(n.type)];if(o)for(var a,s=0;s1&&(d={type:"section",items:s.items.map(function(t){return t.ngModelOptions=s.ngModelOptions,e.isUndefined(t.readonly)&&(t.readonly=s.readonly),t})})}if(a.copyWithIndex=function(t){if(!f[t]&&d){var n=e.copy(d);n.arrayIndex=t,r.traverseForm(n,o(t)),f[t]=n}return f[t]},a.appendToArray=function(){var n=l.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===l.length){var o,u=t("schema.items.type",s);"object"===u?o={}:"array"===u&&(o=[]),l.push(o)}return a.validateArray&&a.validateArray(),c(l),l},a.deleteFromArray=function(e){return l.splice(e,1),a.validateArray&&a.validateArray(),u&&u.$setDirty&&u.$setDirty(),c(l),l},s.titleMap||s.startEmpty===!0||0!==l.length||a.appendToArray(),s.titleMap&&s.titleMap.length>0){a.titleMapValues=[];var h=function(e){a.titleMapValues=[],e=e||[],s.titleMap.forEach(function(t){a.titleMapValues.push(-1!==e.indexOf(t.value))})};h(a.modelArray),a.$watchCollection("modelArray",h),a.$watchCollection("titleMapValues",function(e){if(e){for(var t=a.modelArray;t.length>0;)t.pop();s.titleMap.forEach(function(r,n){e[n]&&t.push(r.value)})}})}if(u){var p;a.validateArray=function(){var e=n.validate(s,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!=="/"+s.key[s.key.length-1]||(u.$setViewValue(a.modelArray),p=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 p}}m()})}}}]),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("sfMessage",["$injector","sfErrorMessage",function(e,t){return{scope:!1,restrict:"EA",link:function(r,n,i){var o=e.has("$sanitize")?e.get("$sanitize"):function(e){return e},a="";i.sfMessage&&(a=r.$eval(i.sfMessage)||"",a=o(a));var s=function(e){if(e&&!r.hasError())n.html(a);else{var i=Object.keys(r.ngModel&&r.ngModel.$error||{}),o=i[0];n.html(o?t.interpolate(o,r.ngModel.$modelValue,r.ngModel.$viewValue,r.form,r.options&&r.options.validationMessage):a)}};s(),r.$watchCollection("ngModel.$error",function(){r.ngModel&&s(r.ngModel.$valid)})}}}]),e.module("schemaForm").directive("sfSchema",["$compile","schemaForm","schemaFormDecorators","sfSelect","sfPath",function(t,r,n,i,o){var a=/[A-Z]/g,s=function(e,t){return t=t||"_",e.replace(a,function(e,r){return(r?t:"")+e.toLowerCase()})};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)}}],replace:!1,restrict:"A",transclude:!0,require:"?form",link:function(a,l,u,c,f){a.formCtrl=c;var m={};f(a,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&&(h.schema=e,h.form=t,p(e,t))}),a.$on("schemaFormRedraw",function(){var e=a.schema,t=a.initialForm||["*"];e&&p(e,t)})}}}]),e.module("schemaForm").directive("schemaValidate",["sfValidator","sfSelect",function(t,r){return{restrict:"A",scope:!1,priority:500,require:"ngModel",link:function(n,i,o,a){n.$emit("schemaFormPropagateNgModelController",a);var s=null,l=function(){return u||(u=n.$eval(o.schemaValidate)),u},u=l();u.copyValueTo&&a.$viewChangeListeners.push(function(){var t=u.copyValueTo;e.forEach(t,function(e){r(e,n.model,a.$modelValue)})});var c=function(e){if(u=l(),!u)return e;if(n.options&&n.options.tv4Validation===!1)return e;var r=t.validate(u,e);return Object.keys(a.$error).filter(function(e){return 0===e.indexOf("tv4-")}).forEach(function(e){a.$setValidity(e,!0)}),r.valid?e:(a.$setValidity("tv4-"+r.error.code,!1),void(s=r.error))};"function"==typeof u.ngModel&&u.ngModel(a),["$parsers","$viewChangeListeners","$formatters"].forEach(function(e){u[e]&&a[e]&&u[e].forEach(function(t){a[e].push(t)})}),["$validators","$asyncValidators"].forEach(function(t){u[t]&&a[t]&&e.forEach(u[t],function(e,r){a[t][r]=e})}),a.$parsers.push(c),n.$on("schemaFormValidate",function(){a.$setDirty?(a.$setDirty(),c(a.$modelValue)):a.$setViewValue(a.$viewValue)}),n.schemaError=function(){return s}}}}]),o}); \ No newline at end of file diff --git a/examples/data/array.json b/examples/data/array.json index e19eb90f9..7521ec3e4 100644 --- a/examples/data/array.json +++ b/examples/data/array.json @@ -44,6 +44,7 @@ }, { "key": "comments", + "onChange" : "say(modelValue.length)", "add": "New", "style": { "add": "btn-success" diff --git a/src/directives/array.js b/src/directives/array.js index 408bb27ea..a595cd690 100644 --- a/src/directives/array.js +++ b/src/directives/array.js @@ -129,6 +129,9 @@ angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sf if (scope.validateArray) { scope.validateArray(); } + + setViewValue(list); + return list; }; @@ -144,6 +147,9 @@ angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sf if (ngModel && ngModel.$setDirty) { ngModel.$setDirty(); } + + setViewValue(list); + return list; }; @@ -253,6 +259,12 @@ angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sf once(); }); + + function setViewValue(value) { + if (ngModel) { + ngModel.$setViewValue(angular.copy(value)); + } + } } }; } diff --git a/src/directives/decorators/bootstrap/array.html b/src/directives/decorators/bootstrap/array.html index 8749b1872..ded892e64 100644 --- a/src/directives/decorators/bootstrap/array.html +++ b/src/directives/decorators/bootstrap/array.html @@ -1,4 +1,4 @@ -

{{ form.title }}

    From 5722713003e3cfc8c38b0e741c82cc191abd553f Mon Sep 17 00:00:00 2001 From: luciformed Date: Wed, 29 Apr 2015 17:35:28 +0100 Subject: [PATCH 2/8] add test for array onChange --- test/directives/schema-form-test.js | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/test/directives/schema-form-test.js b/test/directives/schema-form-test.js index fd12b9ba4..aaa0238a3 100644 --- a/test/directives/schema-form-test.js +++ b/test/directives/schema-form-test.js @@ -2035,4 +2035,49 @@ describe('directive',function(){ }); }); }); + + + it('should handle onChange for array type', function () { + inject(function($compile,$rootScope){ + var scope = $rootScope.$new(); + scope.obj = {}; + + scope.schema = { + "type": "object", + "properties": { + "arr" : { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "default": "Name" + } + } + } + } + } + }; + + scope.form = [{key : "arr", startEmpty : true, onChange: sinon.spy()}]; + + var tmpl = angular.element('
    '); + + $compile(tmpl)(scope); + $rootScope.$apply(); + + + scope.form[0].onChange.should.not.have.been.called; + + + tmpl.find('button.btn-default').click(); + scope.form[0].onChange.should.have.been.calledWith([{name : "Name"}]); + + tmpl.find('button.close').click(); + scope.form[0].onChange.should.have.been.calledWith([]); + + }); + }); + }); From f452238aee6b657e28f03b58889738cf8e74e8a9 Mon Sep 17 00:00:00 2001 From: luciformed Date: Wed, 29 Apr 2015 17:37:20 +0100 Subject: [PATCH 3/8] fix an existing test, that wasn't doing what it was supposed to --- test/directives/schema-form-test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/directives/schema-form-test.js b/test/directives/schema-form-test.js index aaa0238a3..aaa9d929e 100644 --- a/test/directives/schema-form-test.js +++ b/test/directives/schema-form-test.js @@ -252,9 +252,9 @@ describe('directive',function(){ tmpl.children().eq(2).children().eq(0).find('button').length.should.be.equal(1); tmpl.children().eq(2).children().eq(0).find('button').text().should.include('Okidoki'); - scope.form[1].onClick.should.not.have.beenCalled; + scope.form[1].onClick.should.not.have.been.called; tmpl.children().eq(2).children().eq(0).find('button').click(); - scope.form[1].onClick.should.have.beenCalledOnce; + scope.form[1].onClick.should.have.been.calledOnce; }); }); From 56220b4e809e44c4d2574c4fc4c572a606ef05d2 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Mon, 13 Jul 2015 09:25:26 +0200 Subject: [PATCH 4/8] Angular 1.2 fix for arrays A broken watch since it lacked a '.' in 1.2 when object path doesn't use brackets. --- src/directives/array.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/directives/array.js b/src/directives/array.js index b5b039e7e..c387506dc 100644 --- a/src/directives/array.js +++ b/src/directives/array.js @@ -33,6 +33,10 @@ angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sf // It's the (first) array part of the key, '[]' that needs a number // corresponding to an index of the form. var once = scope.$watch(attrs.sfArray, function(form) { + if (!form) { + return; + } + // An array model always needs a key so we know what part of the model // to look at. This makes us a bit incompatible with JSON Form, on the @@ -42,7 +46,8 @@ angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sf // We only modify the same array instance but someone might change the array from // the outside so let's watch for that. We use an ordinary watch since the only case // we're really interested in is if its a new instance. - scope.$watch('model' + sfPath.normalize(form.key), function(value) { + var key = sfPath.normalize(form.key); + scope.$watch('model' + (key[0] !== '[' ? '.' : '') + key, function(value) { list = scope.modelArray = value; }); From a1e3d6522c542d2dab7a7153b88ba187083e4748 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Mon, 13 Jul 2015 09:27:18 +0200 Subject: [PATCH 5/8] gulped --- dist/schema-form.js | 7 ++++++- dist/schema-form.min.js | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/dist/schema-form.js b/dist/schema-form.js index 0fdaaad65..e3653d507 100644 --- a/dist/schema-form.js +++ b/dist/schema-form.js @@ -1520,6 +1520,10 @@ angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sf // It's the (first) array part of the key, '[]' that needs a number // corresponding to an index of the form. var once = scope.$watch(attrs.sfArray, function(form) { + if (!form) { + return; + } + // An array model always needs a key so we know what part of the model // to look at. This makes us a bit incompatible with JSON Form, on the @@ -1529,7 +1533,8 @@ angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sf // We only modify the same array instance but someone might change the array from // the outside so let's watch for that. We use an ordinary watch since the only case // we're really interested in is if its a new instance. - scope.$watch('model' + sfPath.normalize(form.key), function(value) { + var key = sfPath.normalize(form.key); + scope.$watch('model' + (key[0] !== '[' ? '.' : '') + key, function(value) { list = scope.modelArray = value; }); diff --git a/dist/schema-form.min.js b/dist/schema-form.min.js index 86324bdbe..28b1d58d9 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(m.childNodes[0]);d.firstChild.setAttribute("sf-field",l+"["+c+"]");var h={fieldFrag:d,form:u,state:s,path:l+"["+c+"]",build:function(e,r,o){return a(e,t,n,i,r,o)}};"function"==typeof f.builder?f.builder(h):f.builder.forEach(function(e){e(h)}),(o(u,i)||e).appendChild(d)}else{var v=document.createElement(r(t.__name,"-"));v.setAttribute("form",l+"["+c+"]"),(o(u,i)||e).appendChild(v)}return e}},u),u};return{build:function(t,r,n){return a(t,r,function(t){return e.get(t)},n)},builder:n,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,d){return{restrict:"AE",replace:!1,transclude:!1,scope:!0,require:"?^sfSchema",link:function(t,m,p,h){t.$on("schemaFormPropagateNgModelController",function(e,r){e.stopPropagation(),e.preventDefault(),t.ngModel=r}),t.showTitle=function(){return t.form&&t.form.notitle!==!0&&t.form.title},t.listToCheckboxValues=function(t){var r={};return e.forEach(t,function(e){r[e]=!0}),r},t.checkboxValuesToList=function(t){var r=[];return e.forEach(t,function(e,t){e&&r.push(t)}),r},t.buttonClick=function(r,n){e.isFunction(n.onClick)?n.onClick(r,n):e.isString(n.onClick)&&(h?h.evalInParentScope(n.onClick,{$event:r,form:n}):t.$eval(n.onClick,{$event:r,form:n}))},t.evalExpr=function(e,r){return h?h.evalInParentScope(e,r):t.$eval(e,r)},t.evalInScope=function(e,r){return e?t.$eval(e,r):void 0},t.interp=function(e,t){return e&&s(e)(t)},t.hasSuccess=function(){return t.ngModel?t.ngModel.$valid&&(!t.ngModel.$pristine||!t.ngModel.$isEmpty(t.ngModel.$modelValue)):!1},t.hasError=function(){return t.ngModel?t.ngModel.$invalid&&!t.ngModel.$pristine:!1},t.errorMessage=function(e){return c.interpolate(e&&e.code+""||"default",t.ngModel&&t.ngModel.$modelValue||"",t.ngModel&&t.ngModel.$viewValue||"",t.form,t.options&&t.options.validationMessage)};var v=t.$watch(p.form,function(s){if(s){s.ngModelOptions=s.ngModelOptions||{},t.form=s;var c;if("template"===s.type&&s.template)c=u.when(s.template);else{var p="template"===s.type?s.templateUrl:o(n,s);c=a.get(p,{cache:l}).then(function(e){return e.data})}c.then(function(n){if(s.key){var o=s.key?r.stringify(s.key).replace(/"/g,"""):"";n=n.replace(/\$\$value\$\$/g,"model"+("["!==o[0]?".":"")+o)}if(m.html(n),s.condition){var a='evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex})';s.key&&(a='evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex, "modelValue": model'+f.stringify(s.key)+"})"),e.forEach(m.children(),function(e){var t=e.getAttribute("ng-if");e.setAttribute("ng-if",t?"("+t+") || ("+a+")":a)})}i(m.contents())(t)}),s.key&&(t.$on("schemaForm.error."+s.key.join("."),function(e,r,n,i){(n===!0||n===!1)&&(i=n,n=void 0),t.ngModel&&r&&(t.ngModel.$setDirty?t.ngModel.$setDirty():(t.ngModel.$dirty=!0,t.ngModel.$pristine=!1),n&&(s.validationMessage||(s.validationMessage={}),s.validationMessage[r]=n),t.ngModel.$setValidity(r,i===!0),i===!0&&t.$broadcast("schemaFormValidate"))}),t.$on("$destroy",function(){if(!t.externalDestructionInProgress){var e=s.destroyStrategy||t.options&&t.options.destroyStrategy||"remove";if(s.key&&"retain"!==e){var r=t.model;if(s.key.length>1&&(r=d(s.key.slice(0,s.key.length-1),r)),void 0===r)return;var n=s.schema&&s.schema.type||"";"empty"===e&&-1!==n.indexOf("string")?r[s.key.slice(-1)]="":"empty"===e&&-1!==n.indexOf("object")?r[s.key.slice(-1)]={}:"empty"===e&&-1!==n.indexOf("array")?r[s.key.slice(-1)]=[]:"null"===e?r[s.key.slice(-1)]=null:delete r[s.key.slice(-1)]}}})),v()}})}}}])},l=function(r,n,i){i=e.isDefined(i)?i:!1,t.directive("sf"+e.uppercase(r[0])+r.substr(1),function(){return{restrict:"EAC",scope:!0,replace:!0,transclude:i,template:'',link:function(t,n,i){var o={items:"c",titleMap:"c",schema:"c"},a={type:r},l=!0;e.forEach(i,function(r,n){if("$"!==n[0]&&0!==n.indexOf("ng")&&"sfField"!==n){var s=function(r){e.isDefined(r)&&r!==a[n]&&(a[n]=r,l&&a.type&&(a.key||e.isUndefined(i.key))&&(t.form=a,l=!1))};"model"===n?t.$watch(r,function(e){e&&t.model!==e&&(t.model=e)}):"c"===o[n]?t.$watchCollection(r,s):i.$observe(n,s)}})}}})};this.createDecorator=function(t,r){i[t]={__name:t},e.forEach(r,function(e,r){i[t][r]={template:e,replace:!1,builder:[]}}),i[n]||(n=t),a(t)},this.defineDecorator=function(t,r){i[t]={__name:t},e.forEach(r,function(r,n){r.builder=r.builder||[],r.replace=e.isDefined(r.replace)?r.replace:!0,i[t][n]=r}),i[n]||(n=t),a(t)},this.createDirective=l,this.createDirectives=function(t){e.forEach(t,function(e,t){l(t,e)})},this.decorator=function(e){return e=e||n,i[e]},this.addMapping=function(e,t,r,n,o){i[e]&&(i[e][t]={template:r,builder:n,replace:!!o})},this.$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.divisibleBy}}",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 m=function(e){a.titleMapValues=[],e=e||[],l.titleMap.forEach(function(t){a.titleMapValues.push(-1!==e.indexOf(t.value))})};m(a.modelArray),a.$watchCollection("modelArray",m),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 p;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),p=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 p}}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){e.$on("schemaFormPropagateNgModelController",function(t,r){t.stopPropagation(),t.preventDefault(),e.ngModel=r}),e.form=null},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.$watch(n.sfField,function(e){e&&(e.ngModelOptions=e.ngModelOptions||{},t.form=e,e.key&&(t.$on("schemaForm.error."+e.key.join("."),function(r,n,i,o){(i===!0||i===!1)&&(o=i,i=void 0),t.ngModel&&n&&(t.ngModel.$setDirty?t.ngModel.$setDirty():(t.ngModel.$dirty=!0,t.ngModel.$pristine=!1),i&&(e.validationMessage||(e.validationMessage={}),e.validationMessage[n]=i),t.ngModel.$setValidity(n,o===!0),o===!0&&t.$broadcast("schemaFormValidate"))}),t.$on("$destroy",function(){if(!t.externalDestructionInProgress){var r=e.destroyStrategy||t.options&&t.options.destroyStrategy||"remove";if(e.key&&"retain"!==r){var n=t.model;if(e.key.length>1&&(n=u(e.key.slice(0,e.key.length-1),n)),void 0===n)return;var i=e.schema&&e.schema.type||"";"empty"===r&&-1!==i.indexOf("string")?n[e.key.slice(-1)]="":"empty"===r&&-1!==i.indexOf("object")?n[e.key.slice(-1)]={}:"empty"===r&&-1!==i.indexOf("array")?n[e.key.slice(-1)]=[]:"null"===r?n[e.key.slice(-1)]=null:delete n[e.key.slice(-1)]}}})),a())})}}}}]),e.module("schemaForm").directive("sfMessage",["$injector","sfErrorMessage",function(t,r){return{scope:!1,restrict:"EA",link:function(n,i,o){var a=t.has("$sanitize")?t.get("$sanitize"):function(e){return e},l="";o.sfMessage&&n.$watch(o.sfMessage,function(e){e&&(l=a(e),n.ngModel?s(n.ngModel.$valid):s())});var s=function(t){if(t&&!n.hasError())i.html(l);else{var o=[];e.forEach(n.ngModel&&n.ngModel.$error||{},function(e,t){e&&o.push(t)}),o=o.filter(function(e){return"schemaForm"!==e});var a=o[0];a?i.html(r.interpolate(a,n.ngModel.$modelValue,n.ngModel.$viewValue,n.form,n.options&&n.options.validationMessage)):i.html(l)}};s(),n.$watchCollection("ngModel.$error",function(){n.ngModel&&s(n.ngModel.$valid)})}}}]),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)}}],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&&(m.schema=e,m.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})}}}]),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.$watch(o.schemaValidate,function(i){if(i){i.copyValueTo&&a.$viewChangeListeners.push(function(){var t=i.copyValueTo;e.forEach(t,function(e){n(e,r.model,a.$modelValue)})});var o=function(e){if(!i)return e;if(r.options&&r.options.tv4Validation===!1)return e;var n=t.validate(i,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 i.ngModel&&i.ngModel(a),["$parsers","$viewChangeListeners","$formatters"].forEach(function(e){i[e]&&a[e]&&i[e].forEach(function(t){a[e].push(t)})}),["$validators","$asyncValidators"].forEach(function(t){i[t]&&a[t]&&e.forEach(i[t],function(e,r){a[t][r]=e})}),a.$parsers.push(o),a.$validators&&(a.$validators.schemaForm=function(){return!Object.keys(a.$error).some(function(e){return"schemaForm"!==e})}),r.$on("schemaFormValidate",function(){a.$setDirty?(a.$setDirty(),a.$setViewValue(a.$viewValue),a.$commitViewValue(),i.required&&a.$isEmpty(a.$modelValue)&&a.$setValidity("tv4-302",!1)):a.$setViewValue(a.$viewValue)}),r.schemaError=function(){return l},s()}})}}}]),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(m.childNodes[0]);d.firstChild.setAttribute("sf-field",l+"["+c+"]");var h={fieldFrag:d,form:u,state:s,path:l+"["+c+"]",build:function(e,r,o){return a(e,t,n,i,r,o)}};"function"==typeof f.builder?f.builder(h):f.builder.forEach(function(e){e(h)}),(o(u,i)||e).appendChild(d)}else{var v=document.createElement(r(t.__name,"-"));v.setAttribute("form",l+"["+c+"]"),(o(u,i)||e).appendChild(v)}return e}},u),u};return{build:function(t,r,n){return a(t,r,function(t){return e.get(t)},n)},builder:n,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,d){return{restrict:"AE",replace:!1,transclude:!1,scope:!0,require:"?^sfSchema",link:function(t,m,p,h){t.$on("schemaFormPropagateNgModelController",function(e,r){e.stopPropagation(),e.preventDefault(),t.ngModel=r}),t.showTitle=function(){return t.form&&t.form.notitle!==!0&&t.form.title},t.listToCheckboxValues=function(t){var r={};return e.forEach(t,function(e){r[e]=!0}),r},t.checkboxValuesToList=function(t){var r=[];return e.forEach(t,function(e,t){e&&r.push(t)}),r},t.buttonClick=function(r,n){e.isFunction(n.onClick)?n.onClick(r,n):e.isString(n.onClick)&&(h?h.evalInParentScope(n.onClick,{$event:r,form:n}):t.$eval(n.onClick,{$event:r,form:n}))},t.evalExpr=function(e,r){return h?h.evalInParentScope(e,r):t.$eval(e,r)},t.evalInScope=function(e,r){return e?t.$eval(e,r):void 0},t.interp=function(e,t){return e&&s(e)(t)},t.hasSuccess=function(){return t.ngModel?t.ngModel.$valid&&(!t.ngModel.$pristine||!t.ngModel.$isEmpty(t.ngModel.$modelValue)):!1},t.hasError=function(){return t.ngModel?t.ngModel.$invalid&&!t.ngModel.$pristine:!1},t.errorMessage=function(e){return c.interpolate(e&&e.code+""||"default",t.ngModel&&t.ngModel.$modelValue||"",t.ngModel&&t.ngModel.$viewValue||"",t.form,t.options&&t.options.validationMessage)};var v=t.$watch(p.form,function(s){if(s){s.ngModelOptions=s.ngModelOptions||{},t.form=s;var c;if("template"===s.type&&s.template)c=u.when(s.template);else{var p="template"===s.type?s.templateUrl:o(n,s);c=a.get(p,{cache:l}).then(function(e){return e.data})}c.then(function(n){if(s.key){var o=s.key?r.stringify(s.key).replace(/"/g,"""):"";n=n.replace(/\$\$value\$\$/g,"model"+("["!==o[0]?".":"")+o)}if(m.html(n),s.condition){var a='evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex})';s.key&&(a='evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex, "modelValue": model'+f.stringify(s.key)+"})"),e.forEach(m.children(),function(e){var t=e.getAttribute("ng-if");e.setAttribute("ng-if",t?"("+t+") || ("+a+")":a)})}i(m.contents())(t)}),s.key&&(t.$on("schemaForm.error."+s.key.join("."),function(e,r,n,i){(n===!0||n===!1)&&(i=n,n=void 0),t.ngModel&&r&&(t.ngModel.$setDirty?t.ngModel.$setDirty():(t.ngModel.$dirty=!0,t.ngModel.$pristine=!1),n&&(s.validationMessage||(s.validationMessage={}),s.validationMessage[r]=n),t.ngModel.$setValidity(r,i===!0),i===!0&&t.$broadcast("schemaFormValidate"))}),t.$on("$destroy",function(){if(!t.externalDestructionInProgress){var e=s.destroyStrategy||t.options&&t.options.destroyStrategy||"remove";if(s.key&&"retain"!==e){var r=t.model;if(s.key.length>1&&(r=d(s.key.slice(0,s.key.length-1),r)),void 0===r)return;var n=s.schema&&s.schema.type||"";"empty"===e&&-1!==n.indexOf("string")?r[s.key.slice(-1)]="":"empty"===e&&-1!==n.indexOf("object")?r[s.key.slice(-1)]={}:"empty"===e&&-1!==n.indexOf("array")?r[s.key.slice(-1)]=[]:"null"===e?r[s.key.slice(-1)]=null:delete r[s.key.slice(-1)]}}})),v()}})}}}])},l=function(r,n,i){i=e.isDefined(i)?i:!1,t.directive("sf"+e.uppercase(r[0])+r.substr(1),function(){return{restrict:"EAC",scope:!0,replace:!0,transclude:i,template:'',link:function(t,n,i){var o={items:"c",titleMap:"c",schema:"c"},a={type:r},l=!0;e.forEach(i,function(r,n){if("$"!==n[0]&&0!==n.indexOf("ng")&&"sfField"!==n){var s=function(r){e.isDefined(r)&&r!==a[n]&&(a[n]=r,l&&a.type&&(a.key||e.isUndefined(i.key))&&(t.form=a,l=!1))};"model"===n?t.$watch(r,function(e){e&&t.model!==e&&(t.model=e)}):"c"===o[n]?t.$watchCollection(r,s):i.$observe(n,s)}})}}})};this.createDecorator=function(t,r){i[t]={__name:t},e.forEach(r,function(e,r){i[t][r]={template:e,replace:!1,builder:[]}}),i[n]||(n=t),a(t)},this.defineDecorator=function(t,r){i[t]={__name:t},e.forEach(r,function(r,n){r.builder=r.builder||[],r.replace=e.isDefined(r.replace)?r.replace:!0,i[t][n]=r}),i[n]||(n=t),a(t)},this.createDirective=l,this.createDirectives=function(t){e.forEach(t,function(e,t){l(t,e)})},this.decorator=function(e){return e=e||n,i[e]},this.addMapping=function(e,t,r,n,o){i[e]&&(i[e][t]={template:r,builder:n,replace:!!o})},this.$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.divisibleBy}}",101:"{{viewValue}} is less than the allowed minimum of {{schema.minimum}}",102:"{{viewValue}} is equal to the exclusive minimum {{schema.minimum}}",103:"{{viewValue}} is greater than the allowed maximum of {{schema.maximum}}",104:"{{viewValue}} is equal to the exclusive maximum {{schema.maximum}}",105:"Value is not a valid number",200:"String is too short ({{viewValue.length}} chars), minimum {{schema.minLength}}",201:"String is too long ({{viewValue.length}} chars), maximum {{schema.maxLength}}",202:"String does not match pattern: {{schema.pattern}}",300:"Too few properties defined, minimum {{schema.minProperties}}",301:"Too many properties defined, maximum {{schema.maxProperties}}",302:"Required",303:"Additional properties not allowed",304:"Dependency failed - key must exist",400:"Array is too short ({{value.length}}), minimum {{schema.minItems}}",401:"Array is too long ({{value.length}}), maximum {{schema.maxItems}}",402:"Array items are not unique",403:"Additional items not allowed",500:"Format validation failed",501:'Keyword failed: "{{title}}"',600:"Circular $refs",1e3:"Unknown property (not in schema)"};t.number=t[105],t.required=t[302],t.min=t[101],t.max=t[103],t.maxlength=t[201],t.minlength=t[200],t.pattern=t[202],this.setDefaultMessages=function(e){t=e},this.getDefaultMessages=function(){return t},this.setDefaultMessage=function(e,r){t[e]=r},this.$get=["$interpolate",function(r){var n={};return n.defaultMessages=t,n.interpolate=function(n,i,o,a,l){l=l||{};var s=a.validationMessage||{};0===n.indexOf("tv4-")&&(n=n.substring(4));var u=s["default"]||l["default"]||"";[s,l,t].some(function(t){return e.isString(t)||e.isFunction(t)?(u=t,!0):t&&t[n]?(u=t[n],!0):void 0});var c={error:n,value:i,viewValue:o,form:a,schema:a.schema,title:a.title||a.schema&&a.schema.title};return e.isFunction(u)?u(c):r(u)(c)},n}]}),e.module("schemaForm").provider("schemaForm",["sfPathProvider",function(t){var r=function(e){if(Array.isArray(e)&&2==e.length){if("null"===e[0])return e[1];if("null"===e[1])return e[0]}return e},n=function(e){var t=[];return e.forEach(function(e){t.push({name:e,value:e})}),t},i=function(t,r){if(!e.isArray(t)){var n=[];return r?e.forEach(r,function(e,r){n.push({name:t[e],value:e})}):e.forEach(t,function(e,t){n.push({name:e,value:t})}),n}return t},o=function(t,n,i){var o=h[r(n.type)];if(o)for(var a,l=0;l1&&(m={type:"section",items:l.items.map(function(t){return t.ngModelOptions=l.ngModelOptions,e.isUndefined(t.readonly)&&(t.readonly=l.readonly),t})})}if(a.copyWithIndex=function(t){if(!c[t]&&m){var n=e.copy(m);n.arrayIndex=t,r.traverseForm(n,o(t)),c[t]=n}return c[t]},a.appendToArray=function(){var n=s.length,i=a.copyWithIndex(n);if(r.traverseForm(i,function(r){if(r.key){var n;e.isDefined(r["default"])&&(n=r["default"]),e.isDefined(r.schema)&&e.isDefined(r.schema["default"])&&(n=r.schema["default"]),e.isDefined(n)&&t(r.key,a.model,n)}}),n===s.length){var o,u=t("schema.items.type",l);"object"===u?o={}:"array"===u&&(o=[]),s.push(o)}return a.validateArray(),s},a.deleteFromArray=function(e){return s.splice(e,1),a.validateArray(),u&&u.$setDirty&&u.$setDirty(),s},l.titleMap||l.startEmpty===!0||0!==s.length||a.appendToArray(),l.titleMap&&l.titleMap.length>0){a.titleMapValues=[];var p=function(e){a.titleMapValues=[],e=e||[],l.titleMap.forEach(function(t){a.titleMapValues.push(-1!==e.indexOf(t.value))})};p(a.modelArray),a.$watchCollection("modelArray",p),a.$watchCollection("titleMapValues",function(e,t){if(e&&e!==t){for(var r=a.modelArray;r.length>0;)r.pop();l.titleMap.forEach(function(t,n){e[n]&&r.push(t.value)}),a.validateArray()}})}if(u){var h;a.validateArray=function(){var e=n.validate(l,a.modelArray.length>0?a.modelArray:void 0);Object.keys(u.$error).filter(function(e){return 0===e.indexOf("tv4-")}).forEach(function(e){u.$setValidity(e,!0)}),e.valid!==!1||!e.error||""!==e.error.dataPath&&e.error.dataPath!=="/"+l.key[l.key.length-1]||(u.$setViewValue(a.modelArray),h=e.error,u.$setValidity("tv4-"+e.error.code,!1))},a.$on("schemaFormValidate",a.validateArray),a.hasSuccess=function(){return 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){e.$on("schemaFormPropagateNgModelController",function(t,r){t.stopPropagation(),t.preventDefault(),e.ngModel=r}),e.form=null},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.$watch(n.sfField,function(e){e&&(e.ngModelOptions=e.ngModelOptions||{},t.form=e,e.key&&(t.$on("schemaForm.error."+e.key.join("."),function(r,n,i,o){(i===!0||i===!1)&&(o=i,i=void 0),t.ngModel&&n&&(t.ngModel.$setDirty?t.ngModel.$setDirty():(t.ngModel.$dirty=!0,t.ngModel.$pristine=!1),i&&(e.validationMessage||(e.validationMessage={}),e.validationMessage[n]=i),t.ngModel.$setValidity(n,o===!0),o===!0&&t.$broadcast("schemaFormValidate"))}),t.$on("$destroy",function(){if(!t.externalDestructionInProgress){var r=e.destroyStrategy||t.options&&t.options.destroyStrategy||"remove";if(e.key&&"retain"!==r){var n=t.model;if(e.key.length>1&&(n=u(e.key.slice(0,e.key.length-1),n)),void 0===n)return;var i=e.schema&&e.schema.type||"";"empty"===r&&-1!==i.indexOf("string")?n[e.key.slice(-1)]="":"empty"===r&&-1!==i.indexOf("object")?n[e.key.slice(-1)]={}:"empty"===r&&-1!==i.indexOf("array")?n[e.key.slice(-1)]=[]:"null"===r?n[e.key.slice(-1)]=null:delete n[e.key.slice(-1)]}}})),a())})}}}}]),e.module("schemaForm").directive("sfMessage",["$injector","sfErrorMessage",function(t,r){return{scope:!1,restrict:"EA",link:function(n,i,o){var a=t.has("$sanitize")?t.get("$sanitize"):function(e){return e},l="";o.sfMessage&&n.$watch(o.sfMessage,function(e){e&&(l=a(e),n.ngModel?s(n.ngModel.$valid):s())});var s=function(t){if(t&&!n.hasError())i.html(l);else{var o=[];e.forEach(n.ngModel&&n.ngModel.$error||{},function(e,t){e&&o.push(t)}),o=o.filter(function(e){return"schemaForm"!==e});var a=o[0];a?i.html(r.interpolate(a,n.ngModel.$modelValue,n.ngModel.$viewValue,n.form,n.options&&n.options.validationMessage)):i.html(l)}};s(),n.$watchCollection("ngModel.$error",function(){n.ngModel&&s(n.ngModel.$valid)})}}}]),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)}}],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&&(m.schema=e,m.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})}}}]),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.$watch(o.schemaValidate,function(i){if(i){i.copyValueTo&&a.$viewChangeListeners.push(function(){var t=i.copyValueTo;e.forEach(t,function(e){n(e,r.model,a.$modelValue)})});var o=function(e){if(!i)return e;if(r.options&&r.options.tv4Validation===!1)return e;var n=t.validate(i,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 i.ngModel&&i.ngModel(a),["$parsers","$viewChangeListeners","$formatters"].forEach(function(e){i[e]&&a[e]&&i[e].forEach(function(t){a[e].push(t)})}),["$validators","$asyncValidators"].forEach(function(t){i[t]&&a[t]&&e.forEach(i[t],function(e,r){a[t][r]=e})}),a.$parsers.push(o),a.$validators&&(a.$validators.schemaForm=function(){return!Object.keys(a.$error).some(function(e){return"schemaForm"!==e})}),r.$on("schemaFormValidate",function(){a.$setDirty?(a.$setDirty(),a.$setViewValue(a.$viewValue),a.$commitViewValue(),i.required&&a.$isEmpty(a.$modelValue)&&a.$setValidity("tv4-302",!1)):a.$setViewValue(a.$viewValue)}),r.schemaError=function(){return l},s()}})}}}]),o}); \ No newline at end of file From cf7276a5681297bd742e4cfe3f5e6649ede51463 Mon Sep 17 00:00:00 2001 From: luciformed Date: Wed, 29 Apr 2015 17:35:28 +0100 Subject: [PATCH 6/8] add test for array onChange --- test/directives/schema-form-test.js | 44 ++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/test/directives/schema-form-test.js b/test/directives/schema-form-test.js index 51c302618..bc7a9c44c 100644 --- a/test/directives/schema-form-test.js +++ b/test/directives/schema-form-test.js @@ -1827,6 +1827,49 @@ describe('directive',function(){ }); }); + it('should handle onChange for array type', function () { + inject(function($compile,$rootScope){ + var scope = $rootScope.$new(); + scope.obj = {}; + + scope.schema = { + "type": "object", + "properties": { + "arr" : { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "default": "Name" + } + } + } + } + } + }; + + scope.form = [{key : "arr", startEmpty : true, onChange: sinon.spy()}]; + + var tmpl = angular.element('
    '); + + $compile(tmpl)(scope); + $rootScope.$apply(); + + + scope.form[0].onChange.should.not.have.been.called; + + + tmpl.find('button.btn-default').click(); + scope.form[0].onChange.should.have.been.calledWith([{name : "Name"}]); + + tmpl.find('button.close').click(); + scope.form[0].onChange.should.have.been.calledWith([]); + + }); + }); + it('should load template by templateUrl, with template field type',function() { inject(function($compile, $rootScope, $httpBackend){ @@ -2358,7 +2401,6 @@ describe('directive',function(){ }); }); - }); }); From 7dd2d4f5546411c94e65c9bb6c11df250fce31e5 Mon Sep 17 00:00:00 2001 From: luciformed Date: Wed, 29 Apr 2015 17:37:20 +0100 Subject: [PATCH 7/8] fix an existing test, that wasn't doing what it was supposed to --- test/directives/schema-form-test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/directives/schema-form-test.js b/test/directives/schema-form-test.js index bc7a9c44c..fdf2b8b53 100644 --- a/test/directives/schema-form-test.js +++ b/test/directives/schema-form-test.js @@ -252,9 +252,9 @@ describe('directive',function(){ tmpl.children().eq(2).children().eq(0).find('button').length.should.be.equal(1); tmpl.children().eq(2).children().eq(0).find('button').text().should.include('Okidoki'); - scope.form[1].onClick.should.not.have.beenCalled; + scope.form[1].onClick.should.not.have.been.called; tmpl.children().eq(2).children().eq(0).find('button').click(); - scope.form[1].onClick.should.have.beenCalledOnce; + scope.form[1].onClick.should.have.been.calledOnce; }); }); From ebb3382cd88f416230f37bde3f7bb18bb13f878d Mon Sep 17 00:00:00 2001 From: luciformed Date: Mon, 13 Jul 2015 11:19:28 +0100 Subject: [PATCH 8/8] implement onChange support for sf-array --- src/directives/array.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/directives/array.js b/src/directives/array.js index c387506dc..33e3640bb 100644 --- a/src/directives/array.js +++ b/src/directives/array.js @@ -82,6 +82,14 @@ angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sf } + var onChangeHandler = !form.onChange ? angular.noop : function(list) { + if (angular.isFunction(form.onChange)) { + form.onChange(list, form); + } else { + scope.$parent.evalExpr(form.onChange, {'modelValue': list, form: form}); + } + } + // We ceate copies of the form on demand, caching them for // later requests scope.copyWithIndex = function(index) { @@ -132,6 +140,9 @@ angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sf // Trigger validation. scope.validateArray(); + + onChangeHandler(list); + return list; }; @@ -141,6 +152,8 @@ angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sf // Trigger validation. scope.validateArray(); + onChangeHandler(list); + // Angular 1.2 lacks setDirty if (ngModel && ngModel.$setDirty) { ngModel.$setDirty();