diff --git a/dist/schema-form.js b/dist/schema-form.js index 492835885..99a756e45 100644 --- a/dist/schema-form.js +++ b/dist/schema-form.js @@ -44,7 +44,7 @@ angular.module('schemaForm').provider('sfPath', this.parse = ObjectPath.parse; this.stringify = ObjectPath.stringify; this.normalize = ObjectPath.normalize; - this.$get = function () { + this.$get = function() { return ObjectPath; }; }]); @@ -55,7 +55,7 @@ angular.module('schemaForm').provider('sfPath', * @kind function * */ -angular.module('schemaForm').factory('sfSelect', ['sfPath', function (sfPath) { +angular.module('schemaForm').factory('sfSelect', ['sfPath', function(sfPath) { var numRe = /^\d+$/; /** @@ -271,7 +271,7 @@ angular.module('schemaForm').provider('schemaFormDecorators', return scope.form.validationMessage[schemaError.code] || scope.form.validationMessage['default']; } else { - return scope.form.validationMessage.required || + return scope.form.validationMessage.number || scope.form.validationMessage['default'] || scope.form.validationMessage; } @@ -282,8 +282,8 @@ angular.module('schemaForm').provider('schemaFormDecorators', return schemaError.message; //use tv4.js validation message } - //Otherwise we only use required so it must be it. - return 'Required'; + //Otherwise we only have input number not being a number + return 'Not a number'; }; } @@ -586,7 +586,6 @@ angular.module('schemaForm').provider('schemaForm', }; var fieldset = function(name, schema, options) { - if (schema.type === 'object') { var f = stdFormObj(name, schema, options); f.type = 'fieldset'; @@ -640,7 +639,8 @@ angular.module('schemaForm').provider('schemaForm', path: arrPath, required: required || false, lookup: options.lookup, - ignore: options.ignore + ignore: options.ignore, + global: options.global })]; return f; @@ -720,23 +720,27 @@ angular.module('schemaForm').provider('schemaForm', var service = {}; - service.merge = function(schema, form, ignore, options) { + service.merge = function(schema, form, ignore, options, readonly) { form = form || ['*']; options = options || {}; + // Get readonly from root object + readonly = readonly || schema.readonly || schema.readOnly; + var stdForm = service.defaults(schema, ignore, options); + //simple case, we have a "*", just put the stdForm there var idx = form.indexOf('*'); if (idx !== -1) { form = form.slice(0, idx) .concat(stdForm.form) .concat(form.slice(idx + 1)); - return form; } //ok let's merge! //We look at the supplied form and extend it with schema standards var lookup = stdForm.lookup; + return postProcessFn(form.map(function(obj) { //handle the shortcut with just a name @@ -767,26 +771,32 @@ angular.module('schemaForm').provider('schemaForm', }); } + //extend with std form from schema. + + if (obj.key) { + var strid = sfPathProvider.stringify(obj.key); + if (lookup[strid]) { + obj = angular.extend(lookup[strid], obj); + } + } + + // Are we inheriting readonly? + if (readonly === true) { // Inheriting false is not cool. + obj.readonly = true; + } + //if it's a type with items, merge 'em! if (obj.items) { - obj.items = service.merge(schema, obj.items, ignore); + obj.items = service.merge(schema, obj.items, ignore, options, obj.readonly); } //if its has tabs, merge them also! if (obj.tabs) { angular.forEach(obj.tabs, function(tab) { - tab.items = service.merge(schema, tab.items, ignore); + tab.items = service.merge(schema, tab.items, ignore, options, obj.readonly); }); } - //extend with std form from schema. - if (obj.key) { - var str = sfPathProvider.stringify(obj.key); - if (lookup[str]) { - obj = angular.extend(lookup[str], obj); - } - } - // Special case: checkbox // Since have to ternary state we need a default if (obj.type === 'checkbox' && angular.isUndefined(obj.schema['default'])) { @@ -893,26 +903,26 @@ angular.module('schemaForm').factory('sfValidator', [function() { * @return a tv4js result object. */ validator.validate = function(form, value) { - + if (!form) { + return {valid: true}; + } var schema = form.schema; if (!schema) { - //Nothings to Validate - return value; + return {valid: true}; } - //Type cast and validate against schema. - //Basic types of json schema sans array and object - if (schema.type === 'integer') { - value = parseInt(value, 10); - } else if (schema.type === 'number') { - value = parseFloat(value, 10); - } else if (schema.type === 'boolean' && typeof value === 'string') { - if (value === 'true') { - value = true; - } else if (value === 'false') { - value = false; - } + // Input of type text and textareas will give us a viewValue of '' + // when empty, this is a valid value in a schema and does not count as something + // that breaks validation of 'required'. But for our own sanity an empty field should + // not validate if it's required. + if (value === '') { + value = undefined; + } + + // Numbers fields will give a null value, which also means empty field + if (form.type === 'number' && value === null) { + value = undefined; } // Version 4 of JSON Schema has the required property not on the @@ -929,7 +939,6 @@ angular.module('schemaForm').factory('sfValidator', [function() { if (angular.isDefined(value)) { valueWrap[propName] = value; } - return tv4.validateResult(valueWrap, wrap); }; @@ -985,8 +994,16 @@ angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sf // section. Unless there is just one. var subForm = form.items[0]; if (form.items.length > 1) { - subForm = {type: 'section', items: form.items}; + subForm = { + type: 'section', + items: form.items.map(function(item){ + item.ngModelOptions = form.ngModelOptions; + item.readonly = form.readonly; + return item; + }) + }; } + } // We ceate copies of the form on demand, caching them for @@ -1295,6 +1312,9 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', functio return { restrict: 'A', scope: false, + // We want the link function to be *after* the input directives link function so we get access + // the parsed value, ex. a number instead of a string + priority: 1000, require: 'ngModel', link: function(scope, element, attrs, ngModel) { //Since we have scope false this is the same scope @@ -1302,52 +1322,59 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', functio scope.ngModel = ngModel; var error = null; - var form = scope.$eval(attrs.schemaValidate); - // Validate against the schema. - var validate = function(viewValue) { + + var getForm = function() { if (!form) { form = scope.$eval(attrs.schemaValidate); } + return form; + }; + var form = getForm(); - //Still might be undefined - if (!form) { - return viewValue; - } + // Validate against the schema. - // Is required is handled by ng-required? - if (angular.isDefined(attrs.ngRequired) && angular.isUndefined(viewValue)) { - return undefined; - } + // Get in last of the parses so the parsed value has the correct type. + if (ngModel.$validators) { // Angular 1.3 + ngModel.$validators.schema = function(value) { + var result = sfValidator.validate(getForm(), value); + error = result.error; + return result.valid; + }; + } else { - // An empty field gives us the an empty string, which JSON schema - // happily accepts as a proper defined string, but an empty field - // for the user should trigger "required". So we set it to undefined. - if (viewValue === '') { - viewValue = undefined; - } + // Angular 1.2 + ngModel.$parsers.push(function(viewValue) { + form = getForm(); + //Still might be undefined + if (!form) { + return viewValue; + } - var result = sfValidator.validate(form, viewValue); + var result = sfValidator.validate(form, viewValue); - if (result.valid) { - // it is valid - ngModel.$setValidity('schema', true); - return viewValue; - } else { - // it is invalid, return undefined (no model update) - ngModel.$setValidity('schema', false); - error = result.error; - return undefined; - } - }; + if (result.valid) { + // it is valid + ngModel.$setValidity('schema', true); + return viewValue; + } else { + // it is invalid, return undefined (no model update) + ngModel.$setValidity('schema', false); + error = result.error; + return undefined; + } + }); + } - // Unshift onto parsers of the ng-model. - ngModel.$parsers.unshift(validate); // Listen to an event so we can validate the input on request scope.$on('schemaFormValidate', function() { - if (ngModel.$commitViewValue) { - ngModel.$commitViewValue(true); + if (ngModel.$validate) { + ngModel.$validate(); + if (ngModel.$invalid) { // The field must be made dirty so the error message is displayed + ngModel.$dirty = true; + ngModel.$pristine = false; + } } else { ngModel.$setViewValue(ngModel.$viewValue); } @@ -1369,4 +1396,4 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', functio } }; -}]); +}]); \ No newline at end of file diff --git a/dist/schema-form.min.js b/dist/schema-form.min.js index 647911332..9a3970f99 100644 --- a/dist/schema-form.min.js +++ b/dist/schema-form.min.js @@ -1 +1 @@ -var deps=["ObjectPath"];try{angular.module("ngSanitize"),deps.push("ngSanitize")}catch(e){}try{angular.module("ui.sortable"),deps.push("ui.sortable")}catch(e){}try{angular.module("angularSpectrumColorpicker"),deps.push("angularSpectrumColorpicker")}catch(e){}angular.module("schemaForm",deps),angular.module("schemaForm").provider("sfPath",["ObjectPathProvider",function(e){var r={parse:e.parse};r.stringify=1===angular.version.major&&angular.version.minor<3?function(e){return Array.isArray(e)?e.join("."):e.toString()}:e.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}}]),angular.module("schemaForm").factory("sfSelect",["sfPath",function(e){var r=/^\d+$/;return function(t,a,n){a||(a=this);var i="string"==typeof t?e.parse(t):t;if("undefined"!=typeof n&&1===i.length)return a[i[0]]=n,a;"undefined"!=typeof n&&"undefined"==typeof a[i[0]]&&(a[i[0]]=i.length>2&&r.test(i[1])?[]:{});for(var o=a[i[0]],u=1;u',link:function(e,t,a){var n={items:"c",titleMap:"c",schema:"c"},i={type:r},o=!0;angular.forEach(a,function(r,t){if("$"!==t[0]&&0!==t.indexOf("ng")&&"sfField"!==t){var u=function(r){angular.isDefined(r)&&r!==i[t]&&(i[t]=r,o&&i.type&&(i.key||angular.isUndefined(a.key))&&(e.form=i,o=!1))};"model"===t?e.$watch(r,function(r){r&&e.model!==r&&(e.model=r)}):"c"===n[t]?e.$watchCollection(r,u):a.$observe(t,u)}})}}})};this.createDecorator=function(e,r,n){a[e]={mappings:r||{},rules:n||[]},a[t]||(t=e),i(e)},this.createDirective=o,this.createDirectives=function(e){angular.forEach(e,function(e,r){o(r,e)})},this.directive=function(e){return e=e||t,a[e]},this.addMapping=function(e,r,t){a[e]&&(a[e].mappings[r]=t)},this.$get=function(){return{directive:function(e){return a[e]},defaultDecorator:t}},i("sfDecorator")}]),angular.module("schemaForm").provider("schemaForm",["sfPathProvider",function(e){var r=function(e){var r=[];return e.forEach(function(e){r.push({name:e,value:e})}),r},t=function(e,r){if(!angular.isArray(e)){var t=[];return r?angular.forEach(r,function(r){t.push({name:e[r],value:r})}):angular.forEach(e,function(e,r){t.push({name:e,value:r})}),t}return e},a=function(e,r,t){var a=p[r.type];if(a)for(var n,i=0;i1&&(c={type:"section",items:i.items})}if(n.copyWithIndex=function(e){if(!l[e]&&c){var t=angular.copy(c);t.arrayIndex=e,r.traverseForm(t,a(e)),l[e]=t}return l[e]},n.appendToArray=function(){var t=o.length,a=n.copyWithIndex(t);if(r.traverseForm(a,function(r){r.key&&angular.isDefined(r.default)&&e(r.key,n.model,r.default)}),t===o.length){var u,l=e("schema.items.type",i);"object"===l?u={}:"array"===l&&(u=[]),o.push(u)}return n.validateArray&&n.validateArray(),o},n.deleteFromArray=function(e){return o.splice(e,1),n.validateArray&&n.validateArray(),o},i.titleMap||i.startEmpty===!0||0!==o.length||n.appendToArray(),i.titleMap&&i.titleMap.length>0){n.titleMapValues=[];var f=function(e){n.titleMapValues=[],e=e||[],i.titleMap.forEach(function(r){n.titleMapValues.push(-1!==e.indexOf(r.value))})};f(n.modelArray),n.$watchCollection("modelArray",f),n.$watchCollection("titleMapValues",function(e){if(e){for(var r=n.modelArray;r.length>0;)r.shift();i.titleMap.forEach(function(t,a){e[a]&&r.push(t.value)})}})}if(u){var m;n.validateArray=function(){var e=t.validate(i,n.modelArray.length>0?n.modelArray:void 0);e.valid!==!1||!e.error||""!==e.error.dataPath&&e.error.dataPath!=="/"+i.key[i.key.length-1]?u.$setValidity("schema",!0):(u.$setViewValue(n.modelArray),m=e.error,u.$setValidity("schema",!1))},n.$on("schemaFormValidate",n.validateArray),n.hasSuccess=function(){return u.$valid&&!u.$pristine},n.hasError=function(){return u.$invalid},n.schemaError=function(){return m}}s()})}}}]),angular.module("schemaForm").directive("sfChanged",function(){return{require:"ngModel",restrict:"AC",scope:!1,link:function(e,r,t,a){var n=e.$eval(t.sfChanged);n&&n.onChange&&a.$viewChangeListeners.push(function(){angular.isFunction(n.onChange)?n.onChange(a.$modelValue,n):e.evalExpr(n.onChange,{modelValue:a.$modelValue,form:n})})}}}),angular.module("schemaForm").directive("sfSchema",["$compile","schemaForm","schemaFormDecorators","sfSelect",function(e,r,t,a){var n=/[A-Z]/g,i=function(e,r){return r=r||"_",e.replace(n,function(e,t){return(t?r:"")+e.toLowerCase()})};return{scope:{schema:"=sfSchema",initialForm:"=sfForm",model:"=sfModel",options:"=sfOptions"},controller:["$scope",function(e){this.evalInParentScope=function(r,t){return e.$parent.$eval(r,t)}}],replace:!1,restrict:"A",transclude:!0,require:"?form",link:function(n,o,u,l,s){n.formCtrl=l;var c={};s(n,function(e){if(e.addClass("schema-form-ignore"),o.prepend(e),o[0].querySelectorAll){var r=o[0].querySelectorAll("[ng-model]");if(r)for(var t=0;t0){f.schema=l,f.form=s;var m=r.merge(l,s,c,n.options),p=document.createDocumentFragment();n.schemaForm={form:m,schema:l},angular.forEach(m,function(e,r){var a=document.createElement(u.sfDecoratorName||i(t.defaultDecorator,"-"));a.setAttribute("form","schemaForm.form["+r+"]"),p.appendChild(a)}),o.children(":not(.schema-form-ignore)").remove(),o[0].appendChild(p),e(o.children())(n),r.traverseSchema(l,function(e,r){if(angular.isDefined(e["default"])){var t=a(r,n.model);angular.isUndefined(t)&&a(r,n.model,e["default"])}})}})}}}]),angular.module("schemaForm").directive("schemaValidate",["sfValidator",function(e){return{restrict:"A",scope:!1,require:"ngModel",link:function(r,t,a,n){r.ngModel=n;var i=null,o=r.$eval(a.schemaValidate),u=function(t){if(o||(o=r.$eval(a.schemaValidate)),!o)return t;if(angular.isDefined(a.ngRequired)&&angular.isUndefined(t))return void 0;""===t&&(t=void 0);var u=e.validate(o,t);return u.valid?(n.$setValidity("schema",!0),t):(n.$setValidity("schema",!1),void(i=u.error))};n.$parsers.unshift(u),r.$on("schemaFormValidate",function(){n.$commitViewValue?n.$commitViewValue(!0):n.$setViewValue(n.$viewValue)}),r.hasSuccess=function(){return n.$valid&&(!n.$pristine||!n.$isEmpty(n.$modelValue))},r.hasError=function(){return n.$invalid&&!n.$pristine},r.schemaError=function(){return i}}}}]); \ No newline at end of file +var deps=["ObjectPath"];try{angular.module("ngSanitize"),deps.push("ngSanitize")}catch(e){}try{angular.module("ui.sortable"),deps.push("ui.sortable")}catch(e){}try{angular.module("angularSpectrumColorpicker"),deps.push("angularSpectrumColorpicker")}catch(e){}angular.module("schemaForm",deps),angular.module("schemaForm").provider("sfPath",["ObjectPathProvider",function(e){var r={parse:e.parse};r.stringify=1===angular.version.major&&angular.version.minor<3?function(e){return Array.isArray(e)?e.join("."):e.toString()}:e.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}}]),angular.module("schemaForm").factory("sfSelect",["sfPath",function(e){var r=/^\d+$/;return function(t,a,n){a||(a=this);var i="string"==typeof t?e.parse(t):t;if("undefined"!=typeof n&&1===i.length)return a[i[0]]=n,a;"undefined"!=typeof n&&"undefined"==typeof a[i[0]]&&(a[i[0]]=i.length>2&&r.test(i[1])?[]:{});for(var o=a[i[0]],u=1;u',link:function(e,t,a){var n={items:"c",titleMap:"c",schema:"c"},i={type:r},o=!0;angular.forEach(a,function(r,t){if("$"!==t[0]&&0!==t.indexOf("ng")&&"sfField"!==t){var u=function(r){angular.isDefined(r)&&r!==i[t]&&(i[t]=r,o&&i.type&&(i.key||angular.isUndefined(a.key))&&(e.form=i,o=!1))};"model"===t?e.$watch(r,function(r){r&&e.model!==r&&(e.model=r)}):"c"===n[t]?e.$watchCollection(r,u):a.$observe(t,u)}})}}})};this.createDecorator=function(e,r,n){a[e]={mappings:r||{},rules:n||[]},a[t]||(t=e),i(e)},this.createDirective=o,this.createDirectives=function(e){angular.forEach(e,function(e,r){o(r,e)})},this.directive=function(e){return e=e||t,a[e]},this.addMapping=function(e,r,t){a[e]&&(a[e].mappings[r]=t)},this.$get=function(){return{directive:function(e){return a[e]},defaultDecorator:t}},i("sfDecorator")}]),angular.module("schemaForm").provider("schemaForm",["sfPathProvider",function(e){var r=function(e){var r=[];return e.forEach(function(e){r.push({name:e,value:e})}),r},t=function(e,r){if(!angular.isArray(e)){var t=[];return r?angular.forEach(r,function(r){t.push({name:e[r],value:r})}):angular.forEach(e,function(e,r){t.push({name:e,value:r})}),t}return e},a=function(e,r,t){var a=d[r.type];if(a)for(var n,i=0;i1&&(c={type:"section",items:i.items.map(function(e){return e.ngModelOptions=i.ngModelOptions,e.readonly=i.readonly,e})})}if(n.copyWithIndex=function(e){if(!l[e]&&c){var t=angular.copy(c);t.arrayIndex=e,r.traverseForm(t,a(e)),l[e]=t}return l[e]},n.appendToArray=function(){var t=o.length,a=n.copyWithIndex(t);if(r.traverseForm(a,function(r){r.key&&angular.isDefined(r.default)&&e(r.key,n.model,r.default)}),t===o.length){var u,l=e("schema.items.type",i);"object"===l?u={}:"array"===l&&(u=[]),o.push(u)}return n.validateArray&&n.validateArray(),o},n.deleteFromArray=function(e){return o.splice(e,1),n.validateArray&&n.validateArray(),o},i.titleMap||i.startEmpty===!0||0!==o.length||n.appendToArray(),i.titleMap&&i.titleMap.length>0){n.titleMapValues=[];var f=function(e){n.titleMapValues=[],e=e||[],i.titleMap.forEach(function(r){n.titleMapValues.push(-1!==e.indexOf(r.value))})};f(n.modelArray),n.$watchCollection("modelArray",f),n.$watchCollection("titleMapValues",function(e){if(e){for(var r=n.modelArray;r.length>0;)r.shift();i.titleMap.forEach(function(t,a){e[a]&&r.push(t.value)})}})}if(u){var m;n.validateArray=function(){var e=t.validate(i,n.modelArray.length>0?n.modelArray:void 0);e.valid!==!1||!e.error||""!==e.error.dataPath&&e.error.dataPath!=="/"+i.key[i.key.length-1]?u.$setValidity("schema",!0):(u.$setViewValue(n.modelArray),m=e.error,u.$setValidity("schema",!1))},n.$on("schemaFormValidate",n.validateArray),n.hasSuccess=function(){return u.$valid&&!u.$pristine},n.hasError=function(){return u.$invalid},n.schemaError=function(){return m}}s()})}}}]),angular.module("schemaForm").directive("sfChanged",function(){return{require:"ngModel",restrict:"AC",scope:!1,link:function(e,r,t,a){var n=e.$eval(t.sfChanged);n&&n.onChange&&a.$viewChangeListeners.push(function(){angular.isFunction(n.onChange)?n.onChange(a.$modelValue,n):e.evalExpr(n.onChange,{modelValue:a.$modelValue,form:n})})}}}),angular.module("schemaForm").directive("sfSchema",["$compile","schemaForm","schemaFormDecorators","sfSelect",function(e,r,t,a){var n=/[A-Z]/g,i=function(e,r){return r=r||"_",e.replace(n,function(e,t){return(t?r:"")+e.toLowerCase()})};return{scope:{schema:"=sfSchema",initialForm:"=sfForm",model:"=sfModel",options:"=sfOptions"},controller:["$scope",function(e){this.evalInParentScope=function(r,t){return e.$parent.$eval(r,t)}}],replace:!1,restrict:"A",transclude:!0,require:"?form",link:function(n,o,u,l,s){n.formCtrl=l;var c={};s(n,function(e){if(e.addClass("schema-form-ignore"),o.prepend(e),o[0].querySelectorAll){var r=o[0].querySelectorAll("[ng-model]");if(r)for(var t=0;t0){f.schema=l,f.form=s;var m=r.merge(l,s,c,n.options),d=document.createDocumentFragment();n.schemaForm={form:m,schema:l},angular.forEach(m,function(e,r){var a=document.createElement(u.sfDecoratorName||i(t.defaultDecorator,"-"));a.setAttribute("form","schemaForm.form["+r+"]"),d.appendChild(a)}),o.children(":not(.schema-form-ignore)").remove(),o[0].appendChild(d),e(o.children())(n),r.traverseSchema(l,function(e,r){if(angular.isDefined(e["default"])){var t=a(r,n.model);angular.isUndefined(t)&&a(r,n.model,e["default"])}})}})}}}]),angular.module("schemaForm").directive("schemaValidate",["sfValidator",function(e){return{restrict:"A",scope:!1,priority:1e3,require:"ngModel",link:function(r,t,a,n){r.ngModel=n;var i=null,o=function(){return u||(u=r.$eval(a.schemaValidate)),u},u=o();n.$validators?n.$validators.schema=function(r){var t=e.validate(o(),r);return i=t.error,t.valid}:n.$parsers.push(function(r){if(u=o(),!u)return r;var t=e.validate(u,r);return t.valid?(n.$setValidity("schema",!0),r):(n.$setValidity("schema",!1),void(i=t.error))}),r.$on("schemaFormValidate",function(){n.$validate?(n.$validate(),n.$invalid&&(n.$dirty=!0,n.$pristine=!1)):n.$setViewValue(n.$viewValue)}),r.hasSuccess=function(){return n.$valid&&(!n.$pristine||!n.$isEmpty(n.$modelValue))},r.hasError=function(){return n.$invalid&&!n.$pristine},r.schemaError=function(){return i}}}}]); \ No newline at end of file diff --git a/src/directives/schema-form.js b/src/directives/schema-form.js index 775e36bcb..630f600b9 100644 --- a/src/directives/schema-form.js +++ b/src/directives/schema-form.js @@ -80,17 +80,28 @@ angular.module('schemaForm') //make the form available to decorators scope.schemaForm = {form: merged, schema: schema}; - //Create directives from the form definition - angular.forEach(merged, function(obj, i) { - var n = document.createElement(attrs.sfDecoratorName || - snakeCase(schemaFormDecorators.defaultDecorator, '-')); - n.setAttribute('form', 'schemaForm.form[' + i + ']'); - frag.appendChild(n); - }); - //clean all but pre existing html. element.children(':not(.schema-form-ignore)').remove(); + //Create directives from the form definition + angular.forEach(merged,function(obj,i){ + var n = document.createElement(attrs.sfDecorator || snakeCase(schemaFormDecorators.defaultDecorator,'-')); + n.setAttribute('form','schemaForm.form['+i+']'); + var slot; + try { + slot = element[0].querySelector('*[sf-insert-field="' + obj.key + '"]'); + } catch(err) { + // field insertion not supported for complex keys + slot = null; + } + if(slot) { + slot.innerHTML = ""; + slot.appendChild(n); + } else { + frag.appendChild(n); + } + }); + element[0].appendChild(frag); //compile only children diff --git a/test/schema-form-test.js b/test/schema-form-test.js index 70dc6f65b..ff35de2a4 100644 --- a/test/schema-form-test.js +++ b/test/schema-form-test.js @@ -180,6 +180,26 @@ describe('Schema form',function(){ }); }); + it('should preserve existing html and insert fields in matching slots',function(){ + + inject(function($compile,$rootScope){ + var scope = $rootScope.$new(); + scope.person = {}; + + scope.schema = exampleSchema; + + scope.form = ["*"]; + + var tmpl = angular.element('
'); + + $compile(tmpl)(scope); + $rootScope.$apply(); + + tmpl.children().eq(0).is('ul').should.be.true; + tmpl.children().eq(0).find('input').attr('ng-model').should.be.equal('model[\'name\']'); + }); + }); + it('should handle submit buttons',function(){ inject(function($compile,$rootScope){