diff --git a/CHANGELOG b/CHANGELOG index 66bceb2da..956a31f6a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +v0.8.13 +------- + * Bugfix copy the initial form default on form redraw @stanimoto + * Bugfix for revalidating the model after custom validation event is broadcast @cepauskas + * New event value. The form name is now passed in validate and error events @tomsowerby & @LeonardoGentile + v0.8.12 ------- * Bugfix for `condition` builder. It had a typo that broke it. @@ -118,14 +124,14 @@ v0.7.11 v0.7.10 ------ -* Accessability additions, thanks @stramel -* Updates to the gulp tasks, thanks @stramel -* Updated to bower, thanks @Dervisevic -* Updates to documentation & README & Contributing.md, thanks @davidlgj @Dervisevic -* Speed optimization, thanks @stramel -* Call ngModel.$setDirty() when deleting from array, thanks @qstrahl -* Don't override readonly if specified on items individually, thanks @qstrahl -* Update to the examples, thanks @mike-marcacci + * Accessability additions, thanks @stramel + * Updates to the gulp tasks, thanks @stramel + * Updated to bower, thanks @Dervisevic + * Updates to documentation & README & Contributing.md, thanks @davidlgj @Dervisevic + * Speed optimization, thanks @stramel + * Call ngModel.$setDirty() when deleting from array, thanks @qstrahl + * Don't override readonly if specified on items individually, thanks @qstrahl + * Update to the examples, thanks @mike-marcacci v0.7.9 ------ @@ -264,7 +270,7 @@ We're celebrating actual useful functionality by bumping minor version, yay! v0.0.4 ------ -* Fieldsets now properly merge schema defaults. -* Directives for "manual" decorator usage. -* Basic support for buttons. -* Basic support for custom validation error messages. + * Fieldsets now properly merge schema defaults. + * Directives for "manual" decorator usage. + * Basic support for buttons. + * Basic support for custom validation error messages. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c02c039a7..03e9192c0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,6 +11,10 @@ request heck of a lot easier for us. Please avoid including anything from the `dist/` directory as that can make merging harder, and we always generate these files when we make a new release. +**We're currently in transitioning where a large part of the code, i.e. the bootstrap decorator, has been moved to it's own repo. It's here [github.com/Textalk/angular-schema-form-bootstrap](https://github.com/Textalk/angular-schema-form-bootstrap)** + +Feel free to submit issues on the main repo anyway though. + If its a new field type consider making it an add-on instead, especially if it has external dependencies. See [extending Schema Form documentation.](docs/extending.md) diff --git a/README.md b/README.md index f8250e977..b74720c37 100644 --- a/README.md +++ b/README.md @@ -7,28 +7,81 @@ Angular Schema Form [![Build Status](https://img.shields.io/travis/Textalk/angular-schema-form.svg?style=flat-square)](https://travis-ci.org/Textalk/angular-schema-form) [![Build Status](https://img.shields.io/coveralls/jekyll/jekyll.svg?style=flat-square)](https://coveralls.io/r/Textalk/angular-schema-form?branch=development) +Generate forms from JSON schemas using AngularJS! -Are you there? ----------------- -We're still here, in fact we're planning our future right now (2016-03) with a group of **eager contributors**! You may have noticed recent changes to the project GitHub location? Well that is so we can start a discussion in Gitter about how to move forward in a more **open** and **inclusive** way so the burden of development is not all down to David and the good folk at **Textalk**. So please get in touch with the **json-schema-form** org if you want to get involved, otherwise, **get ready for some long overdue progress** soon. +Recent developments +=================== +First, as there has been a rather intensive period of planning and change for this project, there have been important new developments for the project. +Lets get into those first(the normal front page continues below): +The json-schema-form standard +----------------------------- +A standard, json-schema-form, is being created. + +The concept of combining data, JSON Schema and a form definition, is something that isn't just usable in a JavaScript Angular web application, but in any framework, on any platform. +Current ports are [angular-schema-form](https://github.com/json-schema-form/angular-schema-form) and [react-schema-form](https://github.com/networknt/react-schema-form), but delphi-schema-form and laravel-schema-form are planned as well. +To make these ports easier to do, and for everything to work in harmony, a common ground has to be established, a [standard](https://github.com/json-schema-form/json-schema-form). + +Organisational +-------------- +1. ASF has changed into using a more open governance model. This basically means that ASF is now governed by more people. +2. An umbrella organisaton, json-schema-form, has been formed. As you can see, this repo is now a part of that Github organisation, not Textalk. + +Projects +-------- +After a phase of planning, the following list of projects has been decided upon: +https://github.com/json-schema-form/json-schema-form/wiki/Current-projects + +Release 1.0 +----------- +The next major release of ASF will be 1.0. + +The goal for that version is to include the breaking changes that is needed for future developments, like *Of and local $refs: + +* Break out the non-framework specific parts of ASF into a vanilla ES6 module +* Remove the built-in bootstrap decorator, and in doing that require that users wanting to use that load that separately. The reason obviously being the material decorator. + +The reason for the core break out is for all javascript-based ports of the json-schema-form concept to be able to share the same central code base. +Work in that direction is being done in the [json-schema-form-core](https://github.com/json-schema-form/json-schema-form-core) repository. + +Schema builder UI +----------------- +There is now a UI for building schemas and forms being developed at [json-schema-builder repository](https://github.com/json-schema-form/json-schema-builder). + +Ralphael Owino (main author), has a [demo up already](http://ralphowino.github.io/schema-form-builder/#/builder). + +Schema and form repository +-------------------------- +This is now a [repository with template schemas and forms](https://github.com/json-schema-form/json-schema-form-repository). +So far all the [schema.org types](http://schema.org/docs/full.html) has been converted to JSON schema approximations, and also some has been further resolved and had (huge) forms generated. Schema.org is *big*. + +Documentation +------------- +The documentation is evolving, and it is happening mostly on the wiki: +* [The ASF wiki](https://github.com/json-schema-form/angular-schema-form/wiki) +* [The wiki of the json-schema-form organisation](https://github.com/json-schema-form/json-schema-form/wiki) + +New Gitter rooms +---------------- +These are just started. +* Discussions on the [general json-schema-form projects being carried out](https://gitter.im/json-schema-form/json-schema-form-projects) +* Discussions on the [angular-specific ASF projects](https://gitter.im/json-schema-form/angular-schema-form-projects) -Generate forms from JSON schemas using AngularJS! The Blog / The Web Site / The Twitter / The Movie ------------------------------------------------- +================================================= [medium.com/@SchemaFormIO](https://medium.com/@SchemaFormIO) / [schemaform.io](http://schemaform.io) / [@SchemaFormIO](http://twitter.com/SchemaFormIO) / [Movie](https://www.youtube.com/watch?v=duBFMipRq2o) If you use ASF in your project/company please let us know! We'd love to feature you on the site. Demo Time! ----------- +========== [Try out the example page](http://schemaform.io/examples/bootstrap-example.html). Try editing the schema or form definition and see what comes out! Hint: By pressing the 'Save to gist' button (top left), you can save your example into a shareable link. What is it? ----------- +=========== Schema Form is a set of AngularJS directives (and a couple of services). It can do two things to make life easier: diff --git a/bower.json b/bower.json index 312c2f6a1..50dca085a 100644 --- a/bower.json +++ b/bower.json @@ -52,6 +52,7 @@ "angular-mocks": ">= 1.2", "tx-tinymce": ">= 0.0.5", "angular-ui-sortable": ">=0.12.11", - "bootstrap-vertical-tabs": "~1.2.0" + "bootstrap-vertical-tabs": "~1.2.0", + "angular-schema-form-bootstrap": "~0.1.1" } } diff --git a/dist/WHERE_IS_BOOTSTRAP_DECORATOR.md b/dist/WHERE_IS_BOOTSTRAP_DECORATOR.md new file mode 100644 index 000000000..6c0da426c --- /dev/null +++ b/dist/WHERE_IS_BOOTSTRAP_DECORATOR.md @@ -0,0 +1,15 @@ +Hello, + +the `bootstrap-decorator.js` and `bootstrap-decorator.min.js` files has moved +to their project repo, https://github.com/Textalk/angular-schema-form-bootstrap + +You can install it via bower and npm as well. + +```sh +bower install angular-schema-form-bootstrap +``` +or + +```sh +npm install angular-schema-form-bootstrap +``` diff --git a/dist/bootstrap-decorator.js b/dist/bootstrap-decorator.js deleted file mode 100644 index eabcc2c56..000000000 --- a/dist/bootstrap-decorator.js +++ /dev/null @@ -1,90 +0,0 @@ -(function(root, factory) { - if (typeof define === 'function' && define.amd) { - define(['schemaForm'], factory); - } else if (typeof exports === 'object') { - module.exports = factory(require('schemaForm')); - } else { - root.bootstrapDecorator = factory(root.schemaForm); - } -}(this, function(schemaForm) { -angular.module("schemaForm").run(["$templateCache", function($templateCache) {$templateCache.put("directives/decorators/bootstrap/actions-trcl.html","
"); -$templateCache.put("directives/decorators/bootstrap/actions.html","
"); -$templateCache.put("directives/decorators/bootstrap/array.html","
"); -$templateCache.put("directives/decorators/bootstrap/checkbox.html","
"); -$templateCache.put("directives/decorators/bootstrap/checkboxes.html","
"); -$templateCache.put("directives/decorators/bootstrap/default.html","
{{ hasSuccess() ? \'(success)\' : \'(error)\' }}
"); -$templateCache.put("directives/decorators/bootstrap/fieldset-trcl.html","
{{ form.title }}
"); -$templateCache.put("directives/decorators/bootstrap/fieldset.html","
{{ form.title }}
"); -$templateCache.put("directives/decorators/bootstrap/help.html","
"); -$templateCache.put("directives/decorators/bootstrap/radio-buttons.html","
"); -$templateCache.put("directives/decorators/bootstrap/radios-inline.html","
"); -$templateCache.put("directives/decorators/bootstrap/radios.html","
"); -$templateCache.put("directives/decorators/bootstrap/section.html","
"); -$templateCache.put("directives/decorators/bootstrap/select.html","
"); -$templateCache.put("directives/decorators/bootstrap/submit.html","
"); -$templateCache.put("directives/decorators/bootstrap/tabarray.html","
"); -$templateCache.put("directives/decorators/bootstrap/tabs.html","
"); -$templateCache.put("directives/decorators/bootstrap/textarea.html","
");}]); -angular.module('schemaForm').config(['schemaFormDecoratorsProvider', function(decoratorsProvider) { - var base = 'directives/decorators/bootstrap/'; - - decoratorsProvider.defineDecorator('bootstrapDecorator', { - textarea: {template: base + 'textarea.html', replace: false}, - fieldset: {template: base + 'fieldset.html', replace: false}, - /*fieldset: {template: base + 'fieldset.html', replace: true, builder: function(args) { - var children = args.build(args.form.items, args.path + '.items'); - console.log('fieldset children frag', children.childNodes) - args.fieldFrag.childNode.appendChild(children); - }},*/ - array: {template: base + 'array.html', replace: false}, - tabarray: {template: base + 'tabarray.html', replace: false}, - tabs: {template: base + 'tabs.html', replace: false}, - section: {template: base + 'section.html', replace: false}, - conditional: {template: base + 'section.html', replace: false}, - actions: {template: base + 'actions.html', replace: false}, - select: {template: base + 'select.html', replace: false}, - checkbox: {template: base + 'checkbox.html', replace: false}, - checkboxes: {template: base + 'checkboxes.html', replace: false}, - number: {template: base + 'default.html', replace: false}, - password: {template: base + 'default.html', replace: false}, - submit: {template: base + 'submit.html', replace: false}, - button: {template: base + 'submit.html', replace: false}, - radios: {template: base + 'radios.html', replace: false}, - 'radios-inline': {template: base + 'radios-inline.html', replace: false}, - radiobuttons: {template: base + 'radio-buttons.html', replace: false}, - help: {template: base + 'help.html', replace: false}, - 'default': {template: base + 'default.html', replace: false} - }, []); - - //manual use directives - decoratorsProvider.createDirectives({ - textarea: base + 'textarea.html', - select: base + 'select.html', - checkbox: base + 'checkbox.html', - checkboxes: base + 'checkboxes.html', - number: base + 'default.html', - submit: base + 'submit.html', - button: base + 'submit.html', - text: base + 'default.html', - date: base + 'default.html', - password: base + 'default.html', - datepicker: base + 'datepicker.html', - input: base + 'default.html', - radios: base + 'radios.html', - 'radios-inline': base + 'radios-inline.html', - radiobuttons: base + 'radio-buttons.html', - }); - -}]).directive('sfFieldset', function() { - return { - transclude: true, - scope: true, - templateUrl: 'directives/decorators/bootstrap/fieldset-trcl.html', - link: function(scope, element, attrs) { - scope.title = scope.$eval(attrs.title); - } - }; -}); - -return schemaForm; -})); diff --git a/dist/bootstrap-decorator.min.js b/dist/bootstrap-decorator.min.js deleted file mode 100644 index c34d9a39c..000000000 --- a/dist/bootstrap-decorator.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(e,t){"function"==typeof define&&define.amd?define(["schemaForm"],t):"object"==typeof exports?module.exports=t(require("schemaForm")):e.bootstrapDecorator=t(e.schemaForm)}(this,function(e){return angular.module("schemaForm").run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/actions-trcl.html",'
'),e.put("directives/decorators/bootstrap/actions.html",'
'),e.put("directives/decorators/bootstrap/array.html",'
'),e.put("directives/decorators/bootstrap/checkbox.html",'
'),e.put("directives/decorators/bootstrap/checkboxes.html",'
'),e.put("directives/decorators/bootstrap/default.html",'
{{ hasSuccess() ? \'(success)\' : \'(error)\' }}
'),e.put("directives/decorators/bootstrap/fieldset-trcl.html",'
{{ form.title }}
'),e.put("directives/decorators/bootstrap/fieldset.html",'
{{ form.title }}
'),e.put("directives/decorators/bootstrap/help.html",'
'),e.put("directives/decorators/bootstrap/radio-buttons.html",'
'),e.put("directives/decorators/bootstrap/radios-inline.html",'
'),e.put("directives/decorators/bootstrap/radios.html",'
'),e.put("directives/decorators/bootstrap/section.html",'
'),e.put("directives/decorators/bootstrap/select.html",'
'),e.put("directives/decorators/bootstrap/submit.html",'
'),e.put("directives/decorators/bootstrap/tabarray.html",'
'),e.put("directives/decorators/bootstrap/tabs.html",'
'),e.put("directives/decorators/bootstrap/textarea.html",'
')}]),angular.module("schemaForm").config(["schemaFormDecoratorsProvider",function(e){var t="directives/decorators/bootstrap/";e.defineDecorator("bootstrapDecorator",{textarea:{template:t+"textarea.html",replace:!1},fieldset:{template:t+"fieldset.html",replace:!1},array:{template:t+"array.html",replace:!1},tabarray:{template:t+"tabarray.html",replace:!1},tabs:{template:t+"tabs.html",replace:!1},section:{template:t+"section.html",replace:!1},conditional:{template:t+"section.html",replace:!1},actions:{template:t+"actions.html",replace:!1},select:{template:t+"select.html",replace:!1},checkbox:{template:t+"checkbox.html",replace:!1},checkboxes:{template:t+"checkboxes.html",replace:!1},number:{template:t+"default.html",replace:!1},password:{template:t+"default.html",replace:!1},submit:{template:t+"submit.html",replace:!1},button:{template:t+"submit.html",replace:!1},radios:{template:t+"radios.html",replace:!1},"radios-inline":{template:t+"radios-inline.html",replace:!1},radiobuttons:{template:t+"radio-buttons.html",replace:!1},help:{template:t+"help.html",replace:!1},"default":{template:t+"default.html",replace:!1}},[]),e.createDirectives({textarea:t+"textarea.html",select:t+"select.html",checkbox:t+"checkbox.html",checkboxes:t+"checkboxes.html",number:t+"default.html",submit:t+"submit.html",button:t+"submit.html",text:t+"default.html",date:t+"default.html",password:t+"default.html",datepicker:t+"datepicker.html",input:t+"default.html",radios:t+"radios.html","radios-inline":t+"radios-inline.html",radiobuttons:t+"radio-buttons.html"})}]).directive("sfFieldset",function(){return{transclude:!0,scope:!0,templateUrl:"directives/decorators/bootstrap/fieldset-trcl.html",link:function(e,t,s){e.title=e.$eval(s.title)}}}),e}); \ No newline at end of file diff --git a/dist/schema-form.js b/dist/schema-form.js index 7b5b29bde..ff784fa7d 100644 --- a/dist/schema-form.js +++ b/dist/schema-form.js @@ -65,81 +65,6 @@ angular.module('schemaForm').provider('sfPath', }; }]); -/** - * @ngdoc service - * @name sfSelect - * @kind function - * - */ -angular.module('schemaForm').factory('sfSelect', ['sfPath', function(sfPath) { - var numRe = /^\d+$/; - - /** - * @description - * Utility method to access deep properties without - * throwing errors when things are not defined. - * Can also set a value in a deep structure, creating objects when missing - * ex. - * var foo = Select('address.contact.name',obj) - * Select('address.contact.name',obj,'Leeroy') - * - * @param {string} projection A dot path to the property you want to get/set - * @param {object} obj (optional) The object to project on, defaults to 'this' - * @param {Any} valueToSet (opional) The value to set, if parts of the path of - * the projection is missing empty objects will be created. - * @returns {Any|undefined} returns the value at the end of the projection path - * or undefined if there is none. - */ - return function(projection, obj, valueToSet) { - if (!obj) { - obj = this; - } - //Support [] array syntax - var parts = typeof projection === 'string' ? sfPath.parse(projection) : projection; - - if (typeof valueToSet !== 'undefined' && parts.length === 1) { - //special case, just setting one variable - obj[parts[0]] = valueToSet; - return obj; - } - - if (typeof valueToSet !== 'undefined' && - typeof obj[parts[0]] === 'undefined') { - // We need to look ahead to check if array is appropriate - obj[parts[0]] = parts.length > 2 && numRe.test(parts[1]) ? [] : {}; - } - - var value = obj[parts[0]]; - for (var i = 1; i < parts.length; i++) { - // Special case: We allow JSON Form syntax for arrays using empty brackets - // These will of course not work here so we exit if they are found. - if (parts[i] === '') { - return undefined; - } - if (typeof valueToSet !== 'undefined') { - if (i === parts.length - 1) { - //last step. Let's set the value - value[parts[i]] = valueToSet; - return valueToSet; - } else { - // Make sure to create new objects on the way if they are not there. - // We need to look ahead to check if array is appropriate - var tmp = value[parts[i]]; - if (typeof tmp === 'undefined' || tmp === null) { - tmp = numRe.test(parts[i + 1]) ? [] : {}; - value[parts[i]] = tmp; - } - value = tmp; - } - } else if (value) { - //Just get nex value. - value = value[parts[i]]; - } - } - return value; - }; -}]); - // FIXME: type template (using custom builder) angular.module('schemaForm').provider('sfBuilder', ['sfPathProvider', function(sfPathProvider) { @@ -682,6 +607,9 @@ angular.module('schemaForm').provider('schemaFormDecorators', scope.ngModel.$setValidity(error, validity === true); if (validity === true) { + // Re-trigger model validator, that model itself would be re-validated + scope.ngModel.$validate(); + // Setting or removing a validity can change the field to believe its valid // but its not. So lets trigger its validation as well. scope.$broadcast('schemaFormValidate'); @@ -1553,6 +1481,81 @@ angular.module('schemaForm').provider('schemaForm', }]); +/** + * @ngdoc service + * @name sfSelect + * @kind function + * + */ +angular.module('schemaForm').factory('sfSelect', ['sfPath', function(sfPath) { + var numRe = /^\d+$/; + + /** + * @description + * Utility method to access deep properties without + * throwing errors when things are not defined. + * Can also set a value in a deep structure, creating objects when missing + * ex. + * var foo = Select('address.contact.name',obj) + * Select('address.contact.name',obj,'Leeroy') + * + * @param {string} projection A dot path to the property you want to get/set + * @param {object} obj (optional) The object to project on, defaults to 'this' + * @param {Any} valueToSet (opional) The value to set, if parts of the path of + * the projection is missing empty objects will be created. + * @returns {Any|undefined} returns the value at the end of the projection path + * or undefined if there is none. + */ + return function(projection, obj, valueToSet) { + if (!obj) { + obj = this; + } + //Support [] array syntax + var parts = typeof projection === 'string' ? sfPath.parse(projection) : projection; + + if (typeof valueToSet !== 'undefined' && parts.length === 1) { + //special case, just setting one variable + obj[parts[0]] = valueToSet; + return obj; + } + + if (typeof valueToSet !== 'undefined' && + typeof obj[parts[0]] === 'undefined') { + // We need to look ahead to check if array is appropriate + obj[parts[0]] = parts.length > 2 && numRe.test(parts[1]) ? [] : {}; + } + + var value = obj[parts[0]]; + for (var i = 1; i < parts.length; i++) { + // Special case: We allow JSON Form syntax for arrays using empty brackets + // These will of course not work here so we exit if they are found. + if (parts[i] === '') { + return undefined; + } + if (typeof valueToSet !== 'undefined') { + if (i === parts.length - 1) { + //last step. Let's set the value + value[parts[i]] = valueToSet; + return valueToSet; + } else { + // Make sure to create new objects on the way if they are not there. + // We need to look ahead to check if array is appropriate + var tmp = value[parts[i]]; + if (typeof tmp === 'undefined' || tmp === null) { + tmp = numRe.test(parts[i + 1]) ? [] : {}; + value[parts[i]] = tmp; + } + value = tmp; + } + } else if (value) { + //Just get nex value. + value = value[parts[i]]; + } + } + return value; + }; +}]); + /* Common code for validating a value against its form and schema definition */ /* global tv4 */ angular.module('schemaForm').factory('sfValidator', [function() { @@ -2109,6 +2112,9 @@ angular.module('schemaForm').directive('sfField', scope.ngModel.$setValidity(error, validity === true); if (validity === true) { + // Re-trigger model validator, that model itself would be re-validated + scope.ngModel.$validate(); + // Setting or removing a validity can change the field to believe its valid // but its not. So lets trigger its validation as well. scope.$broadcast('schemaFormValidate'); @@ -2677,7 +2683,7 @@ angular.module('schemaForm') // part of the form or schema is chnaged without it being a new instance. scope.$on('schemaFormRedraw', function() { var schema = scope.schema; - var form = scope.initialForm || ['*']; + var form = scope.initialForm ? angular.copy(scope.initialForm) : ['*']; if (schema) { render(schema, form); } @@ -2817,7 +2823,13 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', '$parse var schema = form.schema; // A bit ugly but useful. - scope.validateField = function() { + scope.validateField = function(formName) { + + // If we have specified a form name, and this model is not within + // that form, then leave things be. + if(formName != undefined && ngModel.$$parentForm.$name !== formName) { + return; + } // Special case: arrays // TODO: Can this be generalized in a way that works consistently? @@ -2864,7 +2876,9 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', '$parse }); // Listen to an event so we can validate the input on request - scope.$on('schemaFormValidate', scope.validateField); + scope.$on('schemaFormValidate', function(event, formName) { + scope.validateField(formName); + }); scope.schemaError = function() { return error; diff --git a/dist/schema-form.min.js b/dist/schema-form.min.js index 5f7b06c45..ffa52766f 100644 --- a/dist/schema-form.min.js +++ b/dist/schema-form.min.js @@ -1 +1 @@ -!function(e,t){"function"==typeof define&&define.amd?define(["angular","objectpath","tv4"],t):"object"==typeof exports?module.exports=t(require("angular"),require("objectpath"),require("tv4")):e.schemaForm=t(e.angular,e.objectpath,e.tv4)}(this,function(e,t,r){var n=[];try{e.module("ngSanitize"),n.push("ngSanitize")}catch(i){}try{e.module("ui.sortable"),n.push("ui.sortable")}catch(i){}try{e.module("angularSpectrumColorpicker"),n.push("angularSpectrumColorpicker")}catch(i){}var o=e.module("schemaForm",n);return e.module("schemaForm").provider("sfPath",[function(){var r=window.ObjectPath||t,n={parse:r.parse};1===e.version.major&&e.version.minor<3?n.stringify=function(e){return Array.isArray(e)?e.join("."):e.toString()}:n.stringify=r.stringify,n.normalize=function(e,t){return n.stringify(Array.isArray(e)?e:n.parse(e),t)},this.parse=n.parse,this.stringify=n.stringify,this.normalize=n.normalize,this.$get=function(){return n}}]),e.module("schemaForm").factory("sfSelect",["sfPath",function(e){var t=/^\d+$/;return function(r,n,i){n||(n=this);var o="string"==typeof r?e.parse(r):r;if("undefined"!=typeof i&&1===o.length)return n[o[0]]=i,n;"undefined"!=typeof i&&"undefined"==typeof n[o[0]]&&(n[o[0]]=o.length>2&&t.test(o[1])?[]:{});for(var a=n[o[0]],l=1;l0&&e.fieldFrag.firstChild.setAttribute("ng-model-options",JSON.stringify(e.form.ngModelOptions))},transclusion:function(e){var t=e.fieldFrag.querySelectorAll("[sf-field-transclude]");if(t.length)for(var r=0;r0;)m.appendChild(p.childNodes[0]);var v={fieldFrag:m,form:c,lookup:u,state:s,path:a+"["+f+"]",build:function(e,n,i){return l(e,t,r,o,n,i,u)}},y=c.builder||d.builder;"function"==typeof y?y(v):y.forEach(function(e){e(v)}),(i(c,o)||e).appendChild(m)}else{var g=document.createElement(n(t.__name,"-"));s.arrayCompatFlag?g.setAttribute("form","copyWithIndex($index)"):g.setAttribute("form",a+"["+f+"]"),(i(c,o)||e).appendChild(g)}return e},c),c};return{build:function(t,r,n,i){return l(t,r,function(t,r){return"template"===t.type?t.template:e.get(r.template)},n,void 0,void 0,i)},builder:o,stdBuilders:a,internalBuild:l}}]}]),e.module("schemaForm").provider("schemaFormDecorators",["$compileProvider","sfPathProvider",function(t,r){var n="",i={},o=function(e,t){"sfDecorator"===e&&(e=n);var r=i[e];return r[t.type]?r[t.type].template:r["default"].template},a=function(n){t.directive(n,["$parse","$compile","$http","$templateCache","$interpolate","$q","sfErrorMessage","sfPath","sfSelect",function(t,i,a,l,s,u,c,f,d){return{restrict:"AE",replace:!1,transclude:!1,scope:!0,require:"?^sfSchema",link:function(t,m,p,h){t.$on("schemaFormPropagateNgModelController",function(e,r){e.stopPropagation(),e.preventDefault(),t.ngModel=r}),t.showTitle=function(){return t.form&&t.form.notitle!==!0&&t.form.title},t.listToCheckboxValues=function(t){var r={};return e.forEach(t,function(e){r[e]=!0}),r},t.checkboxValuesToList=function(t){var r=[];return e.forEach(t,function(e,t){e&&r.push(t)}),r},t.buttonClick=function(r,n){e.isFunction(n.onClick)?n.onClick(r,n):e.isString(n.onClick)&&(h?h.evalInParentScope(n.onClick,{$event:r,form:n}):t.$eval(n.onClick,{$event:r,form:n}))},t.evalExpr=function(e,r){return h?h.evalInParentScope(e,r):t.$eval(e,r)},t.evalInScope=function(e,r){return e?t.$eval(e,r):void 0},t.interp=function(e,t){return e&&s(e)(t)},t.hasSuccess=function(){return t.ngModel?t.ngModel.$valid&&(!t.ngModel.$pristine||!t.ngModel.$isEmpty(t.ngModel.$modelValue)):!1},t.hasError=function(){return t.ngModel?t.ngModel.$invalid&&!t.ngModel.$pristine:!1},t.errorMessage=function(e){return c.interpolate(e&&e.code+""||"default",t.ngModel&&t.ngModel.$modelValue||"",t.ngModel&&t.ngModel.$viewValue||"",t.form,t.options&&t.options.validationMessage)};var v=t.$watch(p.form,function(s){if(s){s.ngModelOptions=s.ngModelOptions||{},t.form=s;var c;if("template"===s.type&&s.template)c=u.when(s.template);else{var p="template"===s.type?s.templateUrl:o(n,s);c=a.get(p,{cache:l}).then(function(e){return e.data})}c.then(function(n){if(s.key){var o=s.key?r.stringify(s.key).replace(/"/g,"""):"";n=n.replace(/\$\$value\$\$/g,"model"+("["!==o[0]?".":"")+o)}if(m.html(n),s.condition){var a='evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex})';s.key&&(a='evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex, "modelValue": model'+f.stringify(s.key)+"})"),e.forEach(m.children(),function(e){var t=e.getAttribute("ng-if");e.setAttribute("ng-if",t?"("+t+") || ("+a+")":a)})}i(m.contents())(t)}),s.key&&(t.$on("schemaForm.error."+s.key.join("."),function(e,r,n,i){(n===!0||n===!1)&&(i=n,n=void 0),t.ngModel&&r&&(t.ngModel.$setDirty?t.ngModel.$setDirty():(t.ngModel.$dirty=!0,t.ngModel.$pristine=!1),n&&(s.validationMessage||(s.validationMessage={}),s.validationMessage[r]=n),t.ngModel.$setValidity(r,i===!0),i===!0&&t.$broadcast("schemaFormValidate"))}),t.$on("$destroy",function(){if(!t.externalDestructionInProgress){var e=s.destroyStrategy||t.options&&t.options.destroyStrategy||"remove";if(s.key&&"retain"!==e){var r=t.model;if(s.key.length>1&&(r=d(s.key.slice(0,s.key.length-1),r)),void 0===r)return;var n=s.schema&&s.schema.type||"";"empty"===e&&-1!==n.indexOf("string")?r[s.key.slice(-1)]="":"empty"===e&&-1!==n.indexOf("object")?r[s.key.slice(-1)]={}:"empty"===e&&-1!==n.indexOf("array")?r[s.key.slice(-1)]=[]:"null"===e?r[s.key.slice(-1)]=null:delete r[s.key.slice(-1)]}}})),v()}})}}}])},l=function(r,n,i){i=e.isDefined(i)?i:!1,t.directive("sf"+e.uppercase(r[0])+r.substr(1),function(){return{restrict:"EAC",scope:!0,replace:!0,transclude:i,template:'',link:function(t,n,i){var o={items:"c",titleMap:"c",schema:"c"},a={type:r},l=!0;e.forEach(i,function(r,n){if("$"!==n[0]&&0!==n.indexOf("ng")&&"sfField"!==n){var s=function(r){e.isDefined(r)&&r!==a[n]&&(a[n]=r,l&&a.type&&(a.key||e.isUndefined(i.key))&&(t.form=a,l=!1))};"model"===n?t.$watch(r,function(e){e&&t.model!==e&&(t.model=e)}):"c"===o[n]?t.$watchCollection(r,s):i.$observe(n,s)}})}}})};this.createDecorator=function(t,r){i[t]={__name:t},e.forEach(r,function(e,r){i[t][r]={template:e,replace:!1,builder:[]}}),i[n]||(n=t),a(t)},this.defineDecorator=function(t,r){i[t]={__name:t},e.forEach(r,function(r,n){r.builder=r.builder||[],r.replace=e.isDefined(r.replace)?r.replace:!0,i[t][n]=r}),i[n]||(n=t),a(t)},this.createDirective=l,this.createDirectives=function(t){e.forEach(t,function(e,t){l(t,e)})},this.decorator=function(e){return e=e||n,i[e]},this.addMapping=function(e,t,r,n,o){i[e]&&(i[e][t]={template:r,builder:n,replace:!!o})},this.defineAddOn=function(e,t,r,n){i[e]&&(i[e][t]={template:r,builder:n,replace:!0})},this.$get=function(){return{decorator:function(e){return i[e]||i[n]},defaultDecorator:n}},a("sfDecorator")}]),e.module("schemaForm").provider("sfErrorMessage",function(){var t={"default":"Field does not validate",0:"Invalid type, expected {{schema.type}}",1:"No enum match for: {{viewValue}}",10:'Data does not match any schemas from "anyOf"',11:'Data does not match any schemas from "oneOf"',12:'Data is valid against more than one schema from "oneOf"',13:'Data matches schema from "not"',100:"Value is not a multiple of {{schema.multipleOf}}",101:"{{viewValue}} is less than the allowed minimum of {{schema.minimum}}",102:"{{viewValue}} is equal to the exclusive minimum {{schema.minimum}}",103:"{{viewValue}} is greater than the allowed maximum of {{schema.maximum}}",104:"{{viewValue}} is equal to the exclusive maximum {{schema.maximum}}",105:"Value is not a valid number",200:"String is too short ({{viewValue.length}} chars), minimum {{schema.minLength}}",201:"String is too long ({{viewValue.length}} chars), maximum {{schema.maxLength}}",202:"String does not match pattern: {{schema.pattern}}",300:"Too few properties defined, minimum {{schema.minProperties}}",301:"Too many properties defined, maximum {{schema.maxProperties}}",302:"Required",303:"Additional properties not allowed",304:"Dependency failed - key must exist",400:"Array is too short ({{value.length}}), minimum {{schema.minItems}}",401:"Array is too long ({{value.length}}), maximum {{schema.maxItems}}",402:"Array items are not unique",403:"Additional items not allowed",500:"Format validation failed",501:'Keyword failed: "{{title}}"',600:"Circular $refs",1e3:"Unknown property (not in schema)"};t.number=t[105],t.required=t[302],t.min=t[101],t.max=t[103],t.maxlength=t[201],t.minlength=t[200],t.pattern=t[202],this.setDefaultMessages=function(e){t=e},this.getDefaultMessages=function(){return t},this.setDefaultMessage=function(e,r){t[e]=r},this.$get=["$interpolate",function(r){var n={};return n.defaultMessages=t,n.interpolate=function(n,i,o,a,l){l=l||{};var s=a.validationMessage||{};0===n.indexOf("tv4-")&&(n=n.substring(4));var u=s["default"]||l["default"]||"";[s,l,t].some(function(t){return e.isString(t)||e.isFunction(t)?(u=t,!0):t&&t[n]?(u=t[n],!0):void 0});var c={error:n,value:i,viewValue:o,form:a,schema:a.schema,title:a.title||a.schema&&a.schema.title};return e.isFunction(u)?u(c):r(u)(c)},n}]}),e.module("schemaForm").provider("schemaForm",["sfPathProvider",function(t){var r=function(e){if(Array.isArray(e)&&2==e.length){if("null"===e[0])return e[1];if("null"===e[1])return e[0]}return e},n=function(e){var t=[];return e.forEach(function(e){t.push({name:e,value:e})}),t},i=function(t,r){if(!e.isArray(t)){var n=[];return r?e.forEach(r,function(e,r){n.push({name:t[e],value:e})}):e.forEach(t,function(e,t){n.push({name:e,value:t})}),n}return t},o=function(t,n,i){var o=h[r(n.type)];if(o)for(var a,l=0;l1&&(m={type:"section",items:l.items.map(function(t){return t.ngModelOptions=l.ngModelOptions,e.isUndefined(t.readonly)&&(t.readonly=l.readonly),t})})}if(a.copyWithIndex=function(t){if(!c[t]&&m){var n=e.copy(m);n.arrayIndex=t,r.traverseForm(n,o(t)),c[t]=n}return c[t]},a.appendToArray=function(){var n=s.length,i=a.copyWithIndex(n);if(r.traverseForm(i,function(r){if(r.key){var n;e.isDefined(r["default"])&&(n=r["default"]),e.isDefined(r.schema)&&e.isDefined(r.schema["default"])&&(n=r.schema["default"]),e.isDefined(n)&&t(r.key,a.model,n)}}),n===s.length){var o,u=t("schema.items.type",l);"object"===u?o={}:"array"===u&&(o=[]),s.push(o)}return a.validateArray(),s},a.deleteFromArray=function(e){return s.splice(e,1),a.validateArray(),u&&u.$setDirty&&u.$setDirty(),s},l.titleMap||l.startEmpty===!0||0!==s.length||a.appendToArray(),l.titleMap&&l.titleMap.length>0){a.titleMapValues=[];var p=function(e){a.titleMapValues=[],e=e||[],l.titleMap.forEach(function(t){a.titleMapValues.push(-1!==e.indexOf(t.value))})};p(a.modelArray),a.$watchCollection("modelArray",p),a.$watchCollection("titleMapValues",function(e,t){if(e&&e!==t){for(var r=a.modelArray;r.length>0;)r.pop();l.titleMap.forEach(function(t,n){e[n]&&r.push(t.value)}),a.validateArray()}})}if(u){var h;a.validateArray=function(){var e=n.validate(l,a.modelArray.length>0?a.modelArray:void 0);Object.keys(u.$error).filter(function(e){return 0===e.indexOf("tv4-")}).forEach(function(e){u.$setValidity(e,!0)}),e.valid!==!1||!e.error||""!==e.error.dataPath&&e.error.dataPath!=="/"+l.key[l.key.length-1]||(u.$setViewValue(a.modelArray),h=e.error,u.$setValidity("tv4-"+e.error.code,!1))},a.$on("schemaFormValidate",a.validateArray),a.hasSuccess=function(){return a.options&&a.options.pristine&&a.options.pristine.success===!1?u.$valid&&!u.$pristine&&!u.$isEmpty(u.$modelValue):u.$valid&&(!u.$pristine||!u.$isEmpty(u.$modelValue))},a.hasError=function(){return a.options&&a.options.pristine&&a.options.pristine.errors===!1?u.$invalid&&!u.$pristine:u.$invalid},a.schemaError=function(){return h}}f()}})}}}]),e.module("schemaForm").directive("sfChanged",function(){return{require:"ngModel",restrict:"AC",scope:!1,link:function(t,r,n,i){var o=t.$eval(n.sfChanged);o&&o.onChange&&i.$viewChangeListeners.push(function(){e.isFunction(o.onChange)?o.onChange(i.$modelValue,o):t.evalExpr(o.onChange,{modelValue:i.$modelValue,form:o})})}}}),e.module("schemaForm").directive("sfField",["$parse","$compile","$http","$templateCache","$interpolate","$q","sfErrorMessage","sfPath","sfSelect",function(t,r,n,i,o,a,l,s,u){return{restrict:"AE",replace:!1,transclude:!1,scope:!0,require:"^sfSchema",link:{pre:function(e,t,r,n){e.$on("schemaFormPropagateNgModelController",function(t,r){t.stopPropagation(),t.preventDefault(),e.ngModel=r}),e.form=n.lookup["f"+r.sfField]},post:function(t,r,n,i){t.showTitle=function(){return t.form&&t.form.notitle!==!0&&t.form.title},t.listToCheckboxValues=function(t){var r={};return e.forEach(t,function(e){r[e]=!0}),r},t.checkboxValuesToList=function(t){var r=[];return e.forEach(t,function(e,t){e&&r.push(t)}),r},t.buttonClick=function(r,n){e.isFunction(n.onClick)?n.onClick(r,n):e.isString(n.onClick)&&(i?i.evalInParentScope(n.onClick,{$event:r,form:n}):t.$eval(n.onClick,{$event:r,form:n}))},t.evalExpr=function(e,r){return i?i.evalInParentScope(e,r):t.$eval(e,r)},t.evalInScope=function(e,r){return e?t.$eval(e,r):void 0},t.interp=function(e,t){return e&&o(e)(t)},t.hasSuccess=function(){return t.ngModel?t.options&&t.options.pristine&&t.options.pristine.success===!1?t.ngModel.$valid&&!t.ngModel.$pristine&&!t.ngModel.$isEmpty(t.ngModel.$modelValue):t.ngModel.$valid&&(!t.ngModel.$pristine||!t.ngModel.$isEmpty(t.ngModel.$modelValue)):!1},t.hasError=function(){return t.ngModel?t.options&&t.options.pristine&&t.options.pristine.errors===!1?t.ngModel.$invalid&&!t.ngModel.$pristine:t.ngModel.$invalid:!1},t.errorMessage=function(e){return l.interpolate(e&&e.code+""||"default",t.ngModel&&t.ngModel.$modelValue||"",t.ngModel&&t.ngModel.$viewValue||"",t.form,t.options&&t.options.validationMessage)};var a=t.form;a.key&&(t.$on("schemaForm.error."+a.key.join("."),function(e,r,n,i){(n===!0||n===!1)&&(i=n,n=void 0),t.ngModel&&r&&(t.ngModel.$setDirty?t.ngModel.$setDirty():(t.ngModel.$dirty=!0,t.ngModel.$pristine=!1),n&&(a.validationMessage||(a.validationMessage={}),a.validationMessage[r]=n),t.ngModel.$setValidity(r,i===!0),i===!0&&t.$broadcast("schemaFormValidate"))}),t.$on("$destroy",function(){if(!t.externalDestructionInProgress){var e=a.destroyStrategy||t.options&&t.options.destroyStrategy||"remove";if(a.key&&"retain"!==e){var r=t.model;if(a.key.length>1&&(r=u(a.key.slice(0,a.key.length-1),r)),void 0===r)return;var n=a.schema&&a.schema.type||"";"empty"===e&&-1!==n.indexOf("string")?r[a.key.slice(-1)]="":"empty"===e&&-1!==n.indexOf("object")?r[a.key.slice(-1)]={}:"empty"===e&&-1!==n.indexOf("array")?r[a.key.slice(-1)]=[]:"null"===e?r[a.key.slice(-1)]=null:delete r[a.key.slice(-1)]}}}))}}}}]),e.module("schemaForm").directive("sfMessage",["$injector","sfErrorMessage",function(t,r){var n=t.has("$sanitize")?t.get("$sanitize"):function(e){return e};return{scope:!1,restrict:"EA",link:function(t,i,o){var a="";o.sfMessage&&t.$watch(o.sfMessage,function(e){e&&(a=n(e),u(!!t.ngModel))});var l,s=function(e){e!==l&&(i.html(e),l=e)},u=function(n){if(n)if(t.hasError()){var i=[];e.forEach(t.ngModel&&t.ngModel.$error,function(e,t){e&&i.push(t)}),i=i.filter(function(e){return"schemaForm"!==e});var o=i[0];s(o?r.interpolate(o,t.ngModel.$modelValue,t.ngModel.$viewValue,t.form,t.options&&t.options.validationMessage):a)}else s(a);else s(a)};u();var c=t.$watch("ngModel",function(e){e&&(e.$parsers.push(function(e){return u(!0),e}),e.$formatters.push(function(e){return u(!0),e}),c())});t.$watchCollection("ngModel.$error",function(){u(!!t.ngModel)})}}}]),e.module("schemaForm").directive("sfNewArray",["sfSelect","sfPath","schemaForm",function(t,r,n){return{scope:!1,link:function(i,o,a){i.min=0,i.modelArray=i.$eval(a.sfNewArray);var l=function(){i.modelArray=i.$eval(a.sfNewArray),(!(i.ngModel&&i.ngModel.$pristine&&i.firstDigest)||i.options&&i.options.validateOnRender===!0)&&i.validateField&&i.validateField()},s=function(){i.form&&i.form.onChange&&(e.isFunction(i.form.onChange)?i.form.onChange(i.modelArray,i.form):i.evalExpr(i.form.onChange,{modelValue:i.modelArray,form:i.form}))},u=function(){var e=i.modelArray;if(!e){var n=r.parse(a.sfNewArray);e=[],t(n,i,e),i.modelArray=e}return e},c=i.$watch("form",function(e){if(e){if(e.titleMap||e.startEmpty===!0||i.modelArray&&0!==i.modelArray.length||i.appendToArray(),i.form&&i.form.schema&&i.form.schema.uniqueItems===!0?(i.$watch(a.sfNewArray,l,!0),i.$watch([a.sfNewArray,a.sfNewArray+".length"],s)):i.$watchGroup?i.$watchGroup([a.sfNewArray,a.sfNewArray+".length"],function(){l(),s()}):(i.$watch(a.sfNewArray,function(){l(),s()}),i.$watch(a.sfNewArray+".length",function(){l(),s()})),e.titleMap&&e.titleMap.length>0){i.titleMapValues=[];var t=function(t){i.titleMapValues=[],t=t||[],e.titleMap.forEach(function(e){i.titleMapValues.push(-1!==t.indexOf(e.value))})};t(i.modelArray),i.$watchCollection("modelArray",t),i.$watchCollection("titleMapValues",function(t,r){if(t&&t!==r){for(var n=u();n.length>0;)n.pop();e.titleMap.forEach(function(e,r){t[r]&&n.push(e.value)}),i.validateField&&i.validateField()}})}c()}});i.appendToArray=function(){var r,o=u();if(i.form&&i.form.schema&&i.form.schema.items){var a=i.form.schema.items;a.type&&-1!==a.type.indexOf("object")?(r={},i.options&&i.options.setSchemaDefaults===!1||(r=e.isDefined(a["default"])?a["default"]:r,r&&n.traverseSchema(a,function(n,i){e.isDefined(n["default"])&&t(i,r,n["default"])}))):a.type&&-1!==a.type.indexOf("array")?(r=[],i.options&&i.options.setSchemaDefaults===!1||(r=a["default"]||r)):i.options&&i.options.setSchemaDefaults===!1||(r=a["default"]||r)}return o.push(r),o},i.deleteFromArray=function(e){var t=i.modelArray;return t&&t.splice(e,1),t};var f=function(e){return function(t){t.key&&(t.key[t.key.indexOf("")]=e)}},d={};i.copyWithIndex=function(t){var r=i.form;if(!d[t]){var o=r.items[0];if(r.items.length>1&&(o={type:"section",items:r.items.map(function(t){return t.ngModelOptions=r.ngModelOptions,e.isUndefined(t.readonly)&&(t.readonly=r.readonly),t})}),o){var a=e.copy(o);a.arrayIndex=t,n.traverseForm(a,f(t)),d[t]=a}}return d[t]}}}}]),e.module("schemaForm").directive("sfSchema",["$compile","$http","$templateCache","$q","schemaForm","schemaFormDecorators","sfSelect","sfPath","sfBuilder",function(t,r,n,i,o,a,l,s,u){return{scope:{schema:"=sfSchema",initialForm:"=sfForm",model:"=sfModel",options:"=sfOptions"},controller:["$scope",function(e){this.evalInParentScope=function(t,r){return e.$parent.$eval(t,r)};var t=this;e.lookup=function(e){return e&&(t.lookup=e),t.lookup}}],replace:!1,restrict:"A",transclude:!0,require:"?form",link:function(s,c,f,d,m){s.formCtrl=d;var p={};m(s,function(e){if(e.addClass("schema-form-ignore"),c.prepend(e),c[0].querySelectorAll){var t=c[0].querySelectorAll("[ng-model]");if(t)for(var r=0;r0?i.all(a.map(function(e){return r.get(e.templateUrl,{cache:n}).then(function(t){e.template=t.data})})).then(function(){g(e,t,l)}):g(e,t,l)},g=function(r,n,i){h&&(s.externalDestructionInProgress=!0,h.$destroy(),s.externalDestructionInProgress=!1),h=s.$new(),h.schemaForm={form:i,schema:r},c.children(":not(.schema-form-ignore)").remove();for(var d={},m=c[0].querySelectorAll("*[sf-insert-field]"),p=0;p0&&(v.schema=e,v.form=t,y(e,t))}),s.$on("schemaFormRedraw",function(){var e=s.schema,t=s.initialForm||["*"];e&&y(e,t)}),s.$on("$destroy",function(){s.externalDestructionInProgress=!0}),s.evalExpr=function(e,t){return s.$parent.$eval(e,t)}}}}]),e.module("schemaForm").directive("schemaValidate",["sfValidator","$parse","sfSelect",function(t,r,n){return{restrict:"A",scope:!1,priority:500,require:"ngModel",link:function(r,i,o,a){r.$emit("schemaFormPropagateNgModelController",a);var l=null,s=r.$eval(o.schemaValidate);s.copyValueTo&&a.$viewChangeListeners.push(function(){var t=s.copyValueTo;e.forEach(t,function(e){n(e,r.model,a.$modelValue)})});var u=function(e){if(!s)return e;if(r.options&&r.options.tv4Validation===!1)return e;var n=t.validate(s,e);return Object.keys(a.$error).filter(function(e){return 0===e.indexOf("tv4-")}).forEach(function(e){a.$setValidity(e,!0)}),n.valid?e:(a.$setValidity("tv4-"+n.error.code,!1),l=n.error,a.$validators?e:void 0)};"function"==typeof s.ngModel&&s.ngModel(a),["$parsers","$viewChangeListeners","$formatters"].forEach(function(e){s[e]&&a[e]&&s[e].forEach(function(t){a[e].push(t)})}),["$validators","$asyncValidators"].forEach(function(t){s[t]&&a[t]&&e.forEach(s[t],function(e,r){a[t][r]=e})}),a.$parsers.push(u),a.$validators&&(a.$validators.schemaForm=function(){return!Object.keys(a.$error).some(function(e){return"schemaForm"!==e})});var c=s.schema;r.validateField=function(){c&&-1!==c.type.indexOf("array")&&u(a.$modelValue),a.$setDirty?(a.$setDirty(),a.$setViewValue(a.$viewValue),a.$commitViewValue(),s.required&&a.$isEmpty(a.$modelValue)&&a.$setValidity("tv4-302",!1)):a.$setViewValue(a.$viewValue)},a.$formatters.push(function(e){return!a.$pristine||!r.firstDigest||r.options&&r.options.validateOnRender===!0?(u(a.$modelValue),e):e}),r.$on("schemaFormValidate",r.validateField),r.schemaError=function(){return l}}}}]),o}); \ No newline at end of file +!function(e,t){"function"==typeof define&&define.amd?define(["angular","objectpath","tv4"],t):"object"==typeof exports?module.exports=t(require("angular"),require("objectpath"),require("tv4")):e.schemaForm=t(e.angular,e.objectpath,e.tv4)}(this,function(e,t,r){var n=[];try{e.module("ngSanitize"),n.push("ngSanitize")}catch(i){}try{e.module("ui.sortable"),n.push("ui.sortable")}catch(i){}try{e.module("angularSpectrumColorpicker"),n.push("angularSpectrumColorpicker")}catch(i){}var o=e.module("schemaForm",n);return e.module("schemaForm").provider("sfPath",[function(){var r=window.ObjectPath||t,n={parse:r.parse};1===e.version.major&&e.version.minor<3?n.stringify=function(e){return Array.isArray(e)?e.join("."):e.toString()}:n.stringify=r.stringify,n.normalize=function(e,t){return n.stringify(Array.isArray(e)?e:n.parse(e),t)},this.parse=n.parse,this.stringify=n.stringify,this.normalize=n.normalize,this.$get=function(){return n}}]),e.module("schemaForm").provider("sfBuilder",["sfPathProvider",function(t){var r=/[A-Z]/g,n=function(e,t){return t=t||"_",e.replace(r,function(e,r){return(r?t:"")+e.toLowerCase()})},i=0,o={sfField:function(e){e.fieldFrag.firstChild.setAttribute("sf-field",i),e.lookup["f"+i]=e.form,i++},ngModel:function(e){if(e.form.key){var r=e.form.key;e.state.keyRedaction&&(r=r.slice(e.state.keyRedaction));var n;if(e.state.modelValue)n=e.state.modelValue;else{var i=t.stringify(r).replace(/"/g,""");n=e.state.modelName||"model",i&&(n+=("["!==i[0]?".":"")+i)}for(var o=e.fieldFrag.querySelectorAll("[sf-field-model]"),a=0;a0&&e.fieldFrag.firstChild.setAttribute("ng-model-options",JSON.stringify(e.form.ngModelOptions))},transclusion:function(e){var t=e.fieldFrag.querySelectorAll("[sf-field-transclude]");if(t.length)for(var r=0;r0;)m.appendChild(p.childNodes[0]);var v={fieldFrag:m,form:c,lookup:u,state:s,path:a+"["+f+"]",build:function(e,n,i){return l(e,t,r,o,n,i,u)}},y=c.builder||d.builder;"function"==typeof y?y(v):y.forEach(function(e){e(v)}),(i(c,o)||e).appendChild(m)}else{var g=document.createElement(n(t.__name,"-"));s.arrayCompatFlag?g.setAttribute("form","copyWithIndex($index)"):g.setAttribute("form",a+"["+f+"]"),(i(c,o)||e).appendChild(g)}return e},c),c};return{build:function(t,r,n,i){return l(t,r,function(t,r){return"template"===t.type?t.template:e.get(r.template)},n,void 0,void 0,i)},builder:o,stdBuilders:a,internalBuild:l}}]}]),e.module("schemaForm").provider("schemaFormDecorators",["$compileProvider","sfPathProvider",function(t,r){var n="",i={},o=function(e,t){"sfDecorator"===e&&(e=n);var r=i[e];return r[t.type]?r[t.type].template:r["default"].template},a=function(n){t.directive(n,["$parse","$compile","$http","$templateCache","$interpolate","$q","sfErrorMessage","sfPath","sfSelect",function(t,i,a,l,s,u,c,f,d){return{restrict:"AE",replace:!1,transclude:!1,scope:!0,require:"?^sfSchema",link:function(t,m,p,h){t.$on("schemaFormPropagateNgModelController",function(e,r){e.stopPropagation(),e.preventDefault(),t.ngModel=r}),t.showTitle=function(){return t.form&&t.form.notitle!==!0&&t.form.title},t.listToCheckboxValues=function(t){var r={};return e.forEach(t,function(e){r[e]=!0}),r},t.checkboxValuesToList=function(t){var r=[];return e.forEach(t,function(e,t){e&&r.push(t)}),r},t.buttonClick=function(r,n){e.isFunction(n.onClick)?n.onClick(r,n):e.isString(n.onClick)&&(h?h.evalInParentScope(n.onClick,{$event:r,form:n}):t.$eval(n.onClick,{$event:r,form:n}))},t.evalExpr=function(e,r){return h?h.evalInParentScope(e,r):t.$eval(e,r)},t.evalInScope=function(e,r){return e?t.$eval(e,r):void 0},t.interp=function(e,t){return e&&s(e)(t)},t.hasSuccess=function(){return t.ngModel?t.ngModel.$valid&&(!t.ngModel.$pristine||!t.ngModel.$isEmpty(t.ngModel.$modelValue)):!1},t.hasError=function(){return t.ngModel?t.ngModel.$invalid&&!t.ngModel.$pristine:!1},t.errorMessage=function(e){return c.interpolate(e&&e.code+""||"default",t.ngModel&&t.ngModel.$modelValue||"",t.ngModel&&t.ngModel.$viewValue||"",t.form,t.options&&t.options.validationMessage)};var v=t.$watch(p.form,function(s){if(s){s.ngModelOptions=s.ngModelOptions||{},t.form=s;var c;if("template"===s.type&&s.template)c=u.when(s.template);else{var p="template"===s.type?s.templateUrl:o(n,s);c=a.get(p,{cache:l}).then(function(e){return e.data})}c.then(function(n){if(s.key){var o=s.key?r.stringify(s.key).replace(/"/g,"""):"";n=n.replace(/\$\$value\$\$/g,"model"+("["!==o[0]?".":"")+o)}if(m.html(n),s.condition){var a='evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex})';s.key&&(a='evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex, "modelValue": model'+f.stringify(s.key)+"})"),e.forEach(m.children(),function(e){var t=e.getAttribute("ng-if");e.setAttribute("ng-if",t?"("+t+") || ("+a+")":a)})}i(m.contents())(t)}),s.key&&(t.$on("schemaForm.error."+s.key.join("."),function(e,r,n,i){(n===!0||n===!1)&&(i=n,n=void 0),t.ngModel&&r&&(t.ngModel.$setDirty?t.ngModel.$setDirty():(t.ngModel.$dirty=!0,t.ngModel.$pristine=!1),n&&(s.validationMessage||(s.validationMessage={}),s.validationMessage[r]=n),t.ngModel.$setValidity(r,i===!0),i===!0&&(t.ngModel.$validate(),t.$broadcast("schemaFormValidate")))}),t.$on("$destroy",function(){if(!t.externalDestructionInProgress){var e=s.destroyStrategy||t.options&&t.options.destroyStrategy||"remove";if(s.key&&"retain"!==e){var r=t.model;if(s.key.length>1&&(r=d(s.key.slice(0,s.key.length-1),r)),void 0===r)return;var n=s.schema&&s.schema.type||"";"empty"===e&&-1!==n.indexOf("string")?r[s.key.slice(-1)]="":"empty"===e&&-1!==n.indexOf("object")?r[s.key.slice(-1)]={}:"empty"===e&&-1!==n.indexOf("array")?r[s.key.slice(-1)]=[]:"null"===e?r[s.key.slice(-1)]=null:delete r[s.key.slice(-1)]}}})),v()}})}}}])},l=function(r,n,i){i=e.isDefined(i)?i:!1,t.directive("sf"+e.uppercase(r[0])+r.substr(1),function(){return{restrict:"EAC",scope:!0,replace:!0,transclude:i,template:'',link:function(t,n,i){var o={items:"c",titleMap:"c",schema:"c"},a={type:r},l=!0;e.forEach(i,function(r,n){if("$"!==n[0]&&0!==n.indexOf("ng")&&"sfField"!==n){var s=function(r){e.isDefined(r)&&r!==a[n]&&(a[n]=r,l&&a.type&&(a.key||e.isUndefined(i.key))&&(t.form=a,l=!1))};"model"===n?t.$watch(r,function(e){e&&t.model!==e&&(t.model=e)}):"c"===o[n]?t.$watchCollection(r,s):i.$observe(n,s)}})}}})};this.createDecorator=function(t,r){i[t]={__name:t},e.forEach(r,function(e,r){i[t][r]={template:e,replace:!1,builder:[]}}),i[n]||(n=t),a(t)},this.defineDecorator=function(t,r){i[t]={__name:t},e.forEach(r,function(r,n){r.builder=r.builder||[],r.replace=e.isDefined(r.replace)?r.replace:!0,i[t][n]=r}),i[n]||(n=t),a(t)},this.createDirective=l,this.createDirectives=function(t){e.forEach(t,function(e,t){l(t,e)})},this.decorator=function(e){return e=e||n,i[e]},this.addMapping=function(e,t,r,n,o){i[e]&&(i[e][t]={template:r,builder:n,replace:!!o})},this.defineAddOn=function(e,t,r,n){i[e]&&(i[e][t]={template:r,builder:n,replace:!0})},this.$get=function(){return{decorator:function(e){return i[e]||i[n]},defaultDecorator:n}},a("sfDecorator")}]),e.module("schemaForm").provider("sfErrorMessage",function(){var t={"default":"Field does not validate",0:"Invalid type, expected {{schema.type}}",1:"No enum match for: {{viewValue}}",10:'Data does not match any schemas from "anyOf"',11:'Data does not match any schemas from "oneOf"',12:'Data is valid against more than one schema from "oneOf"',13:'Data matches schema from "not"',100:"Value is not a multiple of {{schema.multipleOf}}",101:"{{viewValue}} is less than the allowed minimum of {{schema.minimum}}",102:"{{viewValue}} is equal to the exclusive minimum {{schema.minimum}}",103:"{{viewValue}} is greater than the allowed maximum of {{schema.maximum}}",104:"{{viewValue}} is equal to the exclusive maximum {{schema.maximum}}",105:"Value is not a valid number",200:"String is too short ({{viewValue.length}} chars), minimum {{schema.minLength}}",201:"String is too long ({{viewValue.length}} chars), maximum {{schema.maxLength}}",202:"String does not match pattern: {{schema.pattern}}",300:"Too few properties defined, minimum {{schema.minProperties}}",301:"Too many properties defined, maximum {{schema.maxProperties}}",302:"Required",303:"Additional properties not allowed",304:"Dependency failed - key must exist",400:"Array is too short ({{value.length}}), minimum {{schema.minItems}}",401:"Array is too long ({{value.length}}), maximum {{schema.maxItems}}",402:"Array items are not unique",403:"Additional items not allowed",500:"Format validation failed",501:'Keyword failed: "{{title}}"',600:"Circular $refs",1e3:"Unknown property (not in schema)"};t.number=t[105],t.required=t[302],t.min=t[101],t.max=t[103],t.maxlength=t[201],t.minlength=t[200],t.pattern=t[202],this.setDefaultMessages=function(e){t=e},this.getDefaultMessages=function(){return t},this.setDefaultMessage=function(e,r){t[e]=r},this.$get=["$interpolate",function(r){var n={};return n.defaultMessages=t,n.interpolate=function(n,i,o,a,l){l=l||{};var s=a.validationMessage||{};0===n.indexOf("tv4-")&&(n=n.substring(4));var u=s["default"]||l["default"]||"";[s,l,t].some(function(t){return e.isString(t)||e.isFunction(t)?(u=t,!0):t&&t[n]?(u=t[n],!0):void 0});var c={error:n,value:i,viewValue:o,form:a,schema:a.schema,title:a.title||a.schema&&a.schema.title};return e.isFunction(u)?u(c):r(u)(c)},n}]}),e.module("schemaForm").provider("schemaForm",["sfPathProvider",function(t){var r=function(e){if(Array.isArray(e)&&2==e.length){if("null"===e[0])return e[1];if("null"===e[1])return e[0]}return e},n=function(e){var t=[];return e.forEach(function(e){t.push({name:e,value:e})}),t},i=function(t,r){if(!e.isArray(t)){var n=[];return r?e.forEach(r,function(e,r){n.push({name:t[e],value:e})}):e.forEach(t,function(e,t){n.push({name:e,value:t})}),n}return t},o=function(t,n,i){var o=h[r(n.type)];if(o)for(var a,l=0;l2&&t.test(o[1])?[]:{});for(var a=n[o[0]],l=1;l1&&(m={type:"section",items:l.items.map(function(t){return t.ngModelOptions=l.ngModelOptions,e.isUndefined(t.readonly)&&(t.readonly=l.readonly),t})})}if(a.copyWithIndex=function(t){if(!c[t]&&m){var n=e.copy(m);n.arrayIndex=t,r.traverseForm(n,o(t)),c[t]=n}return c[t]},a.appendToArray=function(){var n=s.length,i=a.copyWithIndex(n);if(r.traverseForm(i,function(r){if(r.key){var n;e.isDefined(r["default"])&&(n=r["default"]),e.isDefined(r.schema)&&e.isDefined(r.schema["default"])&&(n=r.schema["default"]),e.isDefined(n)&&t(r.key,a.model,n)}}),n===s.length){var o,u=t("schema.items.type",l);"object"===u?o={}:"array"===u&&(o=[]),s.push(o)}return a.validateArray(),s},a.deleteFromArray=function(e){return s.splice(e,1),a.validateArray(),u&&u.$setDirty&&u.$setDirty(),s},l.titleMap||l.startEmpty===!0||0!==s.length||a.appendToArray(),l.titleMap&&l.titleMap.length>0){a.titleMapValues=[];var p=function(e){a.titleMapValues=[],e=e||[],l.titleMap.forEach(function(t){a.titleMapValues.push(-1!==e.indexOf(t.value))})};p(a.modelArray),a.$watchCollection("modelArray",p),a.$watchCollection("titleMapValues",function(e,t){if(e&&e!==t){for(var r=a.modelArray;r.length>0;)r.pop();l.titleMap.forEach(function(t,n){e[n]&&r.push(t.value)}),a.validateArray()}})}if(u){var h;a.validateArray=function(){var e=n.validate(l,a.modelArray.length>0?a.modelArray:void 0);Object.keys(u.$error).filter(function(e){return 0===e.indexOf("tv4-")}).forEach(function(e){u.$setValidity(e,!0)}),e.valid!==!1||!e.error||""!==e.error.dataPath&&e.error.dataPath!=="/"+l.key[l.key.length-1]||(u.$setViewValue(a.modelArray),h=e.error,u.$setValidity("tv4-"+e.error.code,!1))},a.$on("schemaFormValidate",a.validateArray),a.hasSuccess=function(){return a.options&&a.options.pristine&&a.options.pristine.success===!1?u.$valid&&!u.$pristine&&!u.$isEmpty(u.$modelValue):u.$valid&&(!u.$pristine||!u.$isEmpty(u.$modelValue))},a.hasError=function(){return a.options&&a.options.pristine&&a.options.pristine.errors===!1?u.$invalid&&!u.$pristine:u.$invalid},a.schemaError=function(){return h}}f()}})}}}]),e.module("schemaForm").directive("sfChanged",function(){return{require:"ngModel",restrict:"AC",scope:!1,link:function(t,r,n,i){var o=t.$eval(n.sfChanged);o&&o.onChange&&i.$viewChangeListeners.push(function(){e.isFunction(o.onChange)?o.onChange(i.$modelValue,o):t.evalExpr(o.onChange,{modelValue:i.$modelValue,form:o})})}}}),e.module("schemaForm").directive("sfField",["$parse","$compile","$http","$templateCache","$interpolate","$q","sfErrorMessage","sfPath","sfSelect",function(t,r,n,i,o,a,l,s,u){return{restrict:"AE",replace:!1,transclude:!1,scope:!0,require:"^sfSchema",link:{pre:function(e,t,r,n){e.$on("schemaFormPropagateNgModelController",function(t,r){t.stopPropagation(),t.preventDefault(),e.ngModel=r}),e.form=n.lookup["f"+r.sfField]},post:function(t,r,n,i){t.showTitle=function(){return t.form&&t.form.notitle!==!0&&t.form.title},t.listToCheckboxValues=function(t){var r={};return e.forEach(t,function(e){r[e]=!0}),r},t.checkboxValuesToList=function(t){var r=[];return e.forEach(t,function(e,t){e&&r.push(t)}),r},t.buttonClick=function(r,n){e.isFunction(n.onClick)?n.onClick(r,n):e.isString(n.onClick)&&(i?i.evalInParentScope(n.onClick,{$event:r,form:n}):t.$eval(n.onClick,{$event:r,form:n}))},t.evalExpr=function(e,r){return i?i.evalInParentScope(e,r):t.$eval(e,r)},t.evalInScope=function(e,r){return e?t.$eval(e,r):void 0},t.interp=function(e,t){return e&&o(e)(t)},t.hasSuccess=function(){return t.ngModel?t.options&&t.options.pristine&&t.options.pristine.success===!1?t.ngModel.$valid&&!t.ngModel.$pristine&&!t.ngModel.$isEmpty(t.ngModel.$modelValue):t.ngModel.$valid&&(!t.ngModel.$pristine||!t.ngModel.$isEmpty(t.ngModel.$modelValue)):!1},t.hasError=function(){return t.ngModel?t.options&&t.options.pristine&&t.options.pristine.errors===!1?t.ngModel.$invalid&&!t.ngModel.$pristine:t.ngModel.$invalid:!1},t.errorMessage=function(e){return l.interpolate(e&&e.code+""||"default",t.ngModel&&t.ngModel.$modelValue||"",t.ngModel&&t.ngModel.$viewValue||"",t.form,t.options&&t.options.validationMessage)};var a=t.form;a.key&&(t.$on("schemaForm.error."+a.key.join("."),function(e,r,n,i){(n===!0||n===!1)&&(i=n,n=void 0),t.ngModel&&r&&(t.ngModel.$setDirty?t.ngModel.$setDirty():(t.ngModel.$dirty=!0,t.ngModel.$pristine=!1),n&&(a.validationMessage||(a.validationMessage={}),a.validationMessage[r]=n),t.ngModel.$setValidity(r,i===!0),i===!0&&(t.ngModel.$validate(),t.$broadcast("schemaFormValidate")))}),t.$on("$destroy",function(){if(!t.externalDestructionInProgress){var e=a.destroyStrategy||t.options&&t.options.destroyStrategy||"remove";if(a.key&&"retain"!==e){var r=t.model;if(a.key.length>1&&(r=u(a.key.slice(0,a.key.length-1),r)),void 0===r)return;var n=a.schema&&a.schema.type||"";"empty"===e&&-1!==n.indexOf("string")?r[a.key.slice(-1)]="":"empty"===e&&-1!==n.indexOf("object")?r[a.key.slice(-1)]={}:"empty"===e&&-1!==n.indexOf("array")?r[a.key.slice(-1)]=[]:"null"===e?r[a.key.slice(-1)]=null:delete r[a.key.slice(-1)]}}}))}}}}]),e.module("schemaForm").directive("sfMessage",["$injector","sfErrorMessage",function(t,r){var n=t.has("$sanitize")?t.get("$sanitize"):function(e){return e};return{scope:!1,restrict:"EA",link:function(t,i,o){var a="";o.sfMessage&&t.$watch(o.sfMessage,function(e){e&&(a=n(e),u(!!t.ngModel))});var l,s=function(e){e!==l&&(i.html(e),l=e)},u=function(n){if(n)if(t.hasError()){var i=[];e.forEach(t.ngModel&&t.ngModel.$error,function(e,t){e&&i.push(t)}),i=i.filter(function(e){return"schemaForm"!==e});var o=i[0];s(o?r.interpolate(o,t.ngModel.$modelValue,t.ngModel.$viewValue,t.form,t.options&&t.options.validationMessage):a)}else s(a);else s(a)};u();var c=t.$watch("ngModel",function(e){e&&(e.$parsers.push(function(e){return u(!0),e}),e.$formatters.push(function(e){return u(!0),e}),c())});t.$watchCollection("ngModel.$error",function(){u(!!t.ngModel)})}}}]),e.module("schemaForm").directive("sfNewArray",["sfSelect","sfPath","schemaForm",function(t,r,n){return{scope:!1,link:function(i,o,a){i.min=0,i.modelArray=i.$eval(a.sfNewArray);var l=function(){i.modelArray=i.$eval(a.sfNewArray),(!(i.ngModel&&i.ngModel.$pristine&&i.firstDigest)||i.options&&i.options.validateOnRender===!0)&&i.validateField&&i.validateField()},s=function(){i.form&&i.form.onChange&&(e.isFunction(i.form.onChange)?i.form.onChange(i.modelArray,i.form):i.evalExpr(i.form.onChange,{modelValue:i.modelArray,form:i.form}))},u=function(){var e=i.modelArray;if(!e){var n=r.parse(a.sfNewArray);e=[],t(n,i,e),i.modelArray=e}return e},c=i.$watch("form",function(e){if(e){if(e.titleMap||e.startEmpty===!0||i.modelArray&&0!==i.modelArray.length||i.appendToArray(),i.form&&i.form.schema&&i.form.schema.uniqueItems===!0?(i.$watch(a.sfNewArray,l,!0),i.$watch([a.sfNewArray,a.sfNewArray+".length"],s)):i.$watchGroup?i.$watchGroup([a.sfNewArray,a.sfNewArray+".length"],function(){l(),s()}):(i.$watch(a.sfNewArray,function(){l(),s()}),i.$watch(a.sfNewArray+".length",function(){l(),s()})),e.titleMap&&e.titleMap.length>0){i.titleMapValues=[];var t=function(t){i.titleMapValues=[],t=t||[],e.titleMap.forEach(function(e){i.titleMapValues.push(-1!==t.indexOf(e.value))})};t(i.modelArray),i.$watchCollection("modelArray",t),i.$watchCollection("titleMapValues",function(t,r){if(t&&t!==r){for(var n=u();n.length>0;)n.pop();e.titleMap.forEach(function(e,r){t[r]&&n.push(e.value)}),i.validateField&&i.validateField()}})}c()}});i.appendToArray=function(){var r,o=u();if(i.form&&i.form.schema&&i.form.schema.items){var a=i.form.schema.items;a.type&&-1!==a.type.indexOf("object")?(r={},i.options&&i.options.setSchemaDefaults===!1||(r=e.isDefined(a["default"])?a["default"]:r,r&&n.traverseSchema(a,function(n,i){e.isDefined(n["default"])&&t(i,r,n["default"])}))):a.type&&-1!==a.type.indexOf("array")?(r=[],i.options&&i.options.setSchemaDefaults===!1||(r=a["default"]||r)):i.options&&i.options.setSchemaDefaults===!1||(r=a["default"]||r)}return o.push(r),o},i.deleteFromArray=function(e){var t=i.modelArray;return t&&t.splice(e,1),t};var f=function(e){return function(t){t.key&&(t.key[t.key.indexOf("")]=e)}},d={};i.copyWithIndex=function(t){var r=i.form;if(!d[t]){var o=r.items[0];if(r.items.length>1&&(o={type:"section",items:r.items.map(function(t){return t.ngModelOptions=r.ngModelOptions,e.isUndefined(t.readonly)&&(t.readonly=r.readonly),t})}),o){var a=e.copy(o);a.arrayIndex=t,n.traverseForm(a,f(t)),d[t]=a}}return d[t]}}}}]),e.module("schemaForm").directive("sfSchema",["$compile","$http","$templateCache","$q","schemaForm","schemaFormDecorators","sfSelect","sfPath","sfBuilder",function(t,r,n,i,o,a,l,s,u){return{scope:{schema:"=sfSchema",initialForm:"=sfForm",model:"=sfModel",options:"=sfOptions"},controller:["$scope",function(e){this.evalInParentScope=function(t,r){return e.$parent.$eval(t,r)};var t=this;e.lookup=function(e){return e&&(t.lookup=e),t.lookup}}],replace:!1,restrict:"A",transclude:!0,require:"?form",link:function(s,c,f,d,m){s.formCtrl=d;var p={};m(s,function(e){if(e.addClass("schema-form-ignore"),c.prepend(e),c[0].querySelectorAll){var t=c[0].querySelectorAll("[ng-model]");if(t)for(var r=0;r0?i.all(a.map(function(e){return r.get(e.templateUrl,{cache:n}).then(function(t){e.template=t.data})})).then(function(){g(e,t,l)}):g(e,t,l)},g=function(r,n,i){h&&(s.externalDestructionInProgress=!0,h.$destroy(),s.externalDestructionInProgress=!1),h=s.$new(),h.schemaForm={form:i,schema:r},c.children(":not(.schema-form-ignore)").remove();for(var d={},m=c[0].querySelectorAll("*[sf-insert-field]"),p=0;p0&&(v.schema=e,v.form=t,y(e,t))}),s.$on("schemaFormRedraw",function(){var t=s.schema,r=s.initialForm?e.copy(s.initialForm):["*"];t&&y(t,r)}),s.$on("$destroy",function(){s.externalDestructionInProgress=!0}),s.evalExpr=function(e,t){return s.$parent.$eval(e,t)}}}}]),e.module("schemaForm").directive("schemaValidate",["sfValidator","$parse","sfSelect",function(t,r,n){return{restrict:"A",scope:!1,priority:500,require:"ngModel",link:function(r,i,o,a){r.$emit("schemaFormPropagateNgModelController",a);var l=null,s=r.$eval(o.schemaValidate);s.copyValueTo&&a.$viewChangeListeners.push(function(){var t=s.copyValueTo;e.forEach(t,function(e){n(e,r.model,a.$modelValue)})});var u=function(e){if(!s)return e;if(r.options&&r.options.tv4Validation===!1)return e;var n=t.validate(s,e);return Object.keys(a.$error).filter(function(e){return 0===e.indexOf("tv4-")}).forEach(function(e){a.$setValidity(e,!0)}),n.valid?e:(a.$setValidity("tv4-"+n.error.code,!1),l=n.error,a.$validators?e:void 0)};"function"==typeof s.ngModel&&s.ngModel(a),["$parsers","$viewChangeListeners","$formatters"].forEach(function(e){s[e]&&a[e]&&s[e].forEach(function(t){a[e].push(t)})}),["$validators","$asyncValidators"].forEach(function(t){s[t]&&a[t]&&e.forEach(s[t],function(e,r){a[t][r]=e})}),a.$parsers.push(u),a.$validators&&(a.$validators.schemaForm=function(){return!Object.keys(a.$error).some(function(e){return"schemaForm"!==e})});var c=s.schema;r.validateField=function(e){(void 0==e||a.$$parentForm.$name===e)&&(c&&-1!==c.type.indexOf("array")&&u(a.$modelValue),a.$setDirty?(a.$setDirty(),a.$setViewValue(a.$viewValue),a.$commitViewValue(),s.required&&a.$isEmpty(a.$modelValue)&&a.$setValidity("tv4-302",!1)):a.$setViewValue(a.$viewValue))},a.$formatters.push(function(e){return!a.$pristine||!r.firstDigest||r.options&&r.options.validateOnRender===!0?(u(a.$modelValue),e):e}),r.$on("schemaFormValidate",function(e,t){r.validateField(t)}),r.schemaError=function(){return l}}}}]),o}); \ No newline at end of file diff --git a/docs/extending.md b/docs/extending.md index 873d156ee..4361488e1 100644 --- a/docs/extending.md +++ b/docs/extending.md @@ -1,120 +1,139 @@ Extending Schema Form ===================== -Schema Form is designed to be easily extended and there are two basic ways to do it: -1. Add a new type of field -2. Add a new decorator +Schema Form is designed to be easily extended. You can add your own custom fields or completely +change the how the entire form is rendered. -Adding a Field --------------- -To add a new field to Schema Form you need to create a new form type and match that form type with -a template snippet. To do this you use the `schemaFormDecoratorsProvider.addMapping()` function. +A custom field is called an **add-on** and you can find community add-ons listed over at +[schemaform.io](http://schemaform.io). -Ex. from the [datepicker add-on](https://github.com/Textalk/angular-schema-form-datepicker/blob/master/src/bootstrap-datepicker.js#L18) -```javascript - schemaFormDecoratorsProvider.addMapping( - 'bootstrapDecorator', - 'datepicker', - 'directives/decorators/bootstrap/datepicker/datepicker.html' -); -``` +To completely change how the entire field is rendered you need to create what we call a **decorator**. +A decorator is actually a collection of add-ons that at least cover the basic field types +that a schema can default to, but usually a lot more. -The second argument is the name of your new form type, in this case `datepicker`, and the third is -the template we bind to it (the first is the decorator, use `bootstrapDecorator` unless you know -what you are doing). +But before we get into the details of how you define a decorator or an add-on, let's take a look at how schema form builds forms. -What this means is that a form definition like this: -```javascript -$scope.form = [ +How the form is built +---------------------- +Schema Form uses the [sfBuilder](https://github.com/Textalk/angular-schema-form/blob/development/src/services/builder.js) +service to recursively build the DOM elements of the form from a *canonical form definition*, that +is our fancy word for an internal representation of a merge between the schema and the form. + +It's always an array of object and each object at least have the property `type`. If a `type` was +not set in the form definition given to `sf-form` the schema is used to get a default. + +Example canonical form def. +```js +[ { - key: "birthday", - type: "datepicker" + type: 'text' + key: 'name' + }, + { + type: 'fieldset', + item: [ + { + type: 'textarea', + key: 'comment' + } + ] } -]; +] ``` -...will result in the `datepicker.html` template to be used to render that field in the form. -But wait, where is all the code? Basically it's then up to the template to use directives to -implement whatever it likes to do. It does have some help though, lets look at template example and -go through the basics. +#### The actual building +So to build a form from a canonical form definition as in the example above the builder service loops +over and for each type asks the decorator for a template, it adds it to a document fragment. -This is sort of the template for the datepicker: -```html -
- - - +After adding the template it also asks the decorator if that type has a *builder* +function (actually it's usually a list of functions). If so it calls it with the DOM of its template, +the form definition for that field and other useful stuff. This way the builder can modify and +prepare the template depending on options in that fields form object. - -
-``` +Nested fields, as with the fieldset above in the example above, builds it's children with such a +*builder* function. + +This all happens in one large swoop and the finished document fragment is popped inside the form +and then `$compile` is used to kick start it's directives. + + +Creating an add-on +------------------ + +So to create an add-on you need two things, a template and some *builder functions*. Fortunately +schema form got you covered with a couple of standard builders so most of the time you will only +need a template. + +To register your template to be used when the form definition has a specific type you use the +`schemaFormDecoratorsProvider.defineAddOn`. + +Ex. +```js +angular.module('myAddOnModule', ['schemaForm']).config(function(schemaFormDecoratorsProvider, sfBuilderProvider) { -### What's on the scope? -Each form field will be rendered inside a decorator directive, created by the -`schemaFormDecorators` factory service, *do* -[check the source](https://github.com/Textalk/angular-schema-form/blob/master/src/services/decorators.js#L33). + schemaFormDecoratorsProvider.defineAddOn( + 'bootstrapDecorator', // Name of the decorator you want to add to. + 'awesome', // Form type that should render this add-on + 'templates/my/addon.html', // Template name in $templateCache + sfBuilderProvider.stdBuilders // List of builder functions to apply. + ); -This means you have several helper functions and values on scope, most important of this `form`. The -`form` variable contains the merged form definition for that field, i.e. your supplied form object + -the defaults from the schema (it also has its part of the schema under *form.schema*). -This is how you define and use new form field options, whatever is set on the form object is -available here for you to act on. +}); +``` -| Name | What it does | -|----------|----------------| -| form | Form definition object | -| showTitle() | Shorthand for `form && form.notitle !== true && form.title` | -| ngModel | The ngModel controller, this will be on scope if you use either the directive `schema-validate` or `sf-array` | -| evalInScope(expr, locals) | Eval supplied expression, ie scope.$eval | -| evalExpr(expr, locals) | Eval an expression in the parent scope of the main `sf-schema` directive. | -| interp(expr, locals) | Interpolate an expression which may or may not contain expression `{{ }}` sequences | -| buttonClick($event, form) | Use this with ng-click to execute form.onClick | -| hasSuccess() | Shorthand for `ngModel.$valid && (!ngModel.$pristine || !ngModel.$isEmpty(ngModel.$modelValue))` | -| hasError() | Shorthand for `ngModel.$invalid && !ngModel.$pristine` | +The standards builders are `[sfField, ngModel, ngModelOptions, condition]`, see usage details below. -#### Deprecation warning -There is still a `errorMessage` function on scope but it's been deprecated. Please use the -`sf-message` directive instead. +#### The Template +So whats in a template? You usually need a couple of things: + 1. Usually a top level element that surrounds your template is a good idea. The `sfField` builder + slaps on a `sfField` directive that exposes the current form object on scope as `form`. + 1. A `sf-field-model` somewhere so that the `ngModel` builder can add a proper `ngModel` to bind + your model value to. + 1. A `schema-validate="form"` directive on the same element to enable schema validation. + 1. A `
` to display description or error messages. -### The magic $$value$$ -Schema Form wants to play nice with the built in Angular directives for form. Especially `ng-model` -which we want to handle the two way binding against our model value. Also by using `ng-model` we -get all the nice validation states from the `ngModelController` and `FormController` that we all -know and love. +Basic template example: +```html +
+
+ +
+ +``` -To get that working properly we had to resort to a bit of trickery, right before we let Angular -compile the field template we do a simple string replacement of `$$value$$` and replace that -with the path to the current form field on the model, i.e. `form.key`. +**BIG FAT CAVEAT** +Ok, so currently there is something really ugly here. The bootstrap (and material) decorator uses +a build step (gulp-angular-templatecache) to "compile" the template into a javascript file that +basically chucks the template into `$templateCache`. Currently schema form does *not* support +loading the templates any other way. They need to be in `$templateCache` when rendering. -So `ng-model="$$value$$"` becomes something like `ng-model="model['person']['address']['street']"`, -you can see this if you inspect the final form in the browser. +This is really ugly and will be fixed. But you have been warned! -So basically always have a `ng-model="$$value$$"` (Pro tip: ng-model is fine on any element, put - it on the same div as your custom directive and require the ngModelController for full control). -### schema-validate directive -`schema-validate` is a directive that you should put on the same element as your `ng-model`. It is -responsible for validating the value against the schema using [tv4js](https://github.com/geraintluff/tv4) -It takes the form definition as an argument. +Defining a decorator +-------------------- +Defining a decorator is basically the same as defining a lot of add-ons. As with add-ons you use +the `schemaFormDecoratorsProvider` again. This time its +`schemaFormDecoratorsProvider.defineDecorator`. +Ex. +```js +angular.module('myDecoratorModule', ['schemaForm']).config(function(schemaFormDecoratorsProvider, sfBuilderProvider) { -### sf-message directive -Error messages are nice, and the best way to get them is via the `sf-message` directive. It usually -takes `form.description` as an argument so it can show that until an error occurs. + schemaFormDecoratorsProvider.defineDecorator('awesomeDecorator', { + textarea: {template: base + 'textarea.html', builder: sfBuilderProvider.stdBuilders}, + button: {template: base + 'submit.html', builder: sfBuilderProvider.stdBuilders}, + text: {template: base + 'text.html', builder: sfBuilderProvider.stdBuilders}, + // The default is special, if the builder can't find a match it uses the default template. + 'default': {template: base + 'default.html', builder: sfBuilderProvider.stdBuilders} + }, []); +}); +``` ### Setting up schema defaults -So you got this shiny new add-on that adds a fancy field type, but feel a bit bummed out that you +So you got this shiny new add-on or decorator that adds a fancy field type, but feel a bit bummed out that you need to specify it in the form definition all the time? Fear not because you can also add a "rule" to map certain types and conditions in the schema to default to your type. @@ -166,49 +185,136 @@ there can be a delay of a day or so. So [make a bower package](http://bower.io/docs/creating-packages/), add the keyword `angular-schema-form-add-on` and [register it](http://bower.io/docs/creating-packages/#register)! -Decorators ----------- -Decorators are a second way to extend Schema Form, the thought being that you should easily be able -to change *every* field. Maybe you like it old school and want to use bootstrap 2. Or maybe you like -to generate a table with the data instead? Right now there are no other decorators than bootstrap 3. -Basically a *decorator* sets up all the mappings between form types and their respective templates -using the `schemaFormDecoratorsProvider.createDecorator()` function. -```javascript -var base = 'directives/decorators/bootstrap/'; - -schemaFormDecoratorsProvider.createDecorator('bootstrapDecorator', { - textarea: base + 'textarea.html', - fieldset: base + 'fieldset.html', - array: base + 'array.html', - tabarray: base + 'tabarray.html', - tabs: base + 'tabs.html', - section: base + 'section.html', - conditional: base + 'section.html', - actions: base + 'actions.html', - select: base + 'select.html', - checkbox: base + 'checkbox.html', - checkboxes: base + 'checkboxes.html', - number: base + 'default.html', - password: base + 'default.html', - submit: base + 'submit.html', - button: base + 'submit.html', - radios: base + 'radios.html', - 'radios-inline': base + 'radios-inline.html', - radiobuttons: base + 'radio-buttons.html', - help: base + 'help.html', - 'default': base + 'default.html' -}, [ - function(form) { - if (form.readonly && form.key && form.type !== 'fieldset') { - return base + 'readonly.html'; - } - } -]); + +The builders +------------ +A collection of useful builders that cover most cases are in the `sfBuilder` service and is accessable +both from the provider and the service on the property `builders`. There is also a list of "standard" +builders, when in doubt use those. + +```js +angular.module('myMod').config(function(sfBuildersProvider) { + + // Standard builders + sfBuildersProvider.stdBuilders; + + // All builders + sfBuildersProvider.builders.sfField; + sfBuildersProvider.builders.condition; + sfBuildersProvider.builders.ngModel; + sfBuildersProvider.builders.ngModelOptions; + sfBuildersProvider.builders.simpleTransclusion; + sfBuildersProvider.builders.transclusion; + sfBuildersProvider.builders.array; + +}); +``` + +Currently the standard builders are: +```js +var stdBuilders = [ + builders.sfField, + builders.ngModel, + builders.ngModelOptions, + builders.condition +]; +``` + + +### builders.sfField +The `sfField` builder adds the `sf-field="..."` directive to *the first child element* in the template, +giving it a correct value. The value is an id number that identifies that specific form object. + +The `sf-field` directive exports the form definition object as `form` on scope and as a lot of useful functions. + +As a rule of thumb you always want this builder. + +### builders.condition +The `condition` builder checks the form definition for the option `condition`. If it's present it adds a +`ng-if` to all top level elements in the template. + +You usually want this as well. + +### builder.ngModel +The `ngModel` builder is maybe the most important builder. It makes sure you get a proper binding to +your model value. + +The `ngModel` builder queries the DOM of the template for all elements that have the attribute `sf-field-model`. Your template may have several of them. `sf-field-model` is *not* a directive, +but depending on it's value the `ngModel` builder will take three different actions. + + +#### sf-field-model +Just `sf-field-model` or `sf-field-model=""` tells the builder to add a `ng-model` directive to this element. +This is a common use case. + +Ex: +DOM before `ngModel` builder: +```html +
+ +
+``` +DOM after `ngModel` builder: +```html +
+ +
+``` + +#### sf-field-model="" +Given a value the `ngModel` builder will treat that value as a *attribute name* and instead of slapping +on a `ng-model` set the specified attributes value. It sets it to the same value as the `ng-model` would have gotten. + +Ex: +DOM before `ngModel` builder: +```html +
+ +
``` -`schemaFormDecoratorsProvider.createDecorator(name, mapping, rules)` takes a name argument, a mapping object -(type -> template) and an optional list of rule functions. +DOM after `ngModel` builder: +```html +
+ +
+``` + +#### sf-field-model="replaceAll" +With the special value *replaceAll* the `ngModel` builder will instead loop over every attribute on the +element and do a string replacement of `"$$value$$"` with the proper model value. + +Ex: +DOM before `ngModel` builder: +```html +
+ +
+``` +DOM after `ngModel` builder: +```html +
+ +
+``` + +### builders.ngModelOptions +If the form definition has a `ngModelOptions` option specified this builder will slap on a `ng-model-options` +attribute to *the first child element* in the template. + + +### builder.simpleTransclusion +The `simpleTransclusion` builder will recurse and build form items, useful for fieldsets etc. This builder +is simple because it only appends children to the first child element and only checks `form.items`. + -When the decorator is trying to match a form type against a tempate it first executes all the rules -in order. If one returns that is used as template, otherwise it checks the mappings. +Useful directives +----------------- +TODO: more in depth about schema-validate, sf-messages and sf-field diff --git a/docs/index.md b/docs/index.md index 9c5093ff5..be05f831e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,3 +1,14 @@ + +IMPORTANT +========= + +**Angular Schema Form is undergoing a refactoring and the "bootstrap decorator", i.e. the part with +all the HTML has been moved to [github.com/Textalk/angular-schema-form-bootstrap](https://github.com/Textalk/angular-schema-form-bootstrap).** + +The documentation below, especially form options is therefore somewhat bootstrap decorator +specific. The docs is undergoing updating. + + Documentation ============= @@ -43,8 +54,13 @@ Documentation Basic Usage ----------- -First, expose your schema, form, and model to the $scope. -Don't forget to load the `schemaForm` module. +After installing, load the `schemaForm` module in your module definition. + +Then, in your controller, expose your [schema](http://json-schema.org/), +form, and [model](https://docs.angularjs.org/guide/databinding) to the $scope. +Your schema defines your data structure, the form definition +draws on this definition to define the user interface, and the +model binds the user input to the controller. ```javascript angular.module('myModule', ['schemaForm']) @@ -72,7 +88,8 @@ angular.module('myModule', ['schemaForm']) } ``` -Then load them into Schema Form using the `sfSchema`, `sfForm`, and `sfModel` directives. +Then, in your template, load them into Schema Form using the +`sfSchema`, `sfForm`, and `sfModel` directives. ```html
@@ -941,7 +958,7 @@ element to the select. { type: "actions", items: [ - { type: 'submit', title: 'Ok' } + { type: 'submit', title: 'Ok' }, { type: 'button', title: 'Cancel', onClick: "cancel()" } ] } @@ -953,7 +970,7 @@ We can change this with ```style``` attribute: { type: "actions", items: [ - { type: 'submit', style: 'btn-success', title: 'Ok' } + { type: 'submit', style: 'btn-success', title: 'Ok' }, { type: 'button', style: 'btn-info', title: 'Cancel', onClick: "cancel()" } ] } @@ -967,7 +984,7 @@ the ```sf-schema``` directive. ```javascript [ - { type: 'submit', title: 'Ok', onClick: function(){ ... } } + { type: 'submit', title: 'Ok', onClick: function(){ ... } }, { type: 'button', title: 'Cancel', onClick: "cancel()" } [ ``` @@ -976,7 +993,7 @@ The submit and other buttons have btn-default as default. We can change this with ```style``` attribute: ```javascript [ - { type: 'submit', style: 'btn-warning', title: 'Ok', onClick: function(){ ... } } + { type: 'submit', style: 'btn-warning', title: 'Ok', onClick: function(){ ... } }, { type: 'button', style: 'btn-danger', title: 'Cancel', onClick: "cancel()" } [ ``` @@ -1021,7 +1038,7 @@ function FormCtrl($scope) { type: "radiobuttons", titleMap: [ { value: "one", name: "One" }, - { value, "two", name: "More..." } + { value: "two", name: "More..." } ] } ]; @@ -1222,8 +1239,9 @@ could be changed using attribute `add`, see example below. If you like to have drag and drop reordering of arrays you also need [ui-sortable](https://github.com/angular-ui/ui-sortable) and its dependencies [jQueryUI](http://jqueryui.com/), see *ui-sortable* documentation for details of -what parts of jQueryUI that is needed. You can safely ignore these if you don't -need the reordering. +what parts of jQueryUI that is needed. You can also pass options to the *ui-sortable* directive +by including a `sortOptions` key on the form object. Check the *ui-sortable* documentation +for a complete list of available options. You can safely ignore these if you don't need the reordering. In the form definition you can refer to properties of an array item by the empty bracket notation. In the `key` simply end the name of the array with `[]` diff --git a/examples/bootstrap-example.html b/examples/bootstrap-example.html deleted file mode 100644 index 89e57fdf3..000000000 --- a/examples/bootstrap-example.html +++ /dev/null @@ -1,389 +0,0 @@ - - - - - Bootstrap Schema Form example - - - - - - - - - - - - -
-

Schema Form Example

-
-
-

The Generated Form

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

Model

-
{{pretty()}}
-
-
-

Select Example

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

Form

-
-

Schema

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

Demo of custom validators, async validators and parsers

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

Array Example

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

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

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

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

Tabbed Array Example

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

" - }, - { - "key": "comments", - "type": "tabarray", - "add": "New", - "remove": "Delete", - "style": { - "remove": "btn-danger" - }, - "title": "{{ value.name || 'Tab '+$index }}", - "items": [ - "comments[].name", - "comments[].email", - { - "key": "comments[].comment", - "type": "textarea" - } - ] - }, - { - "type": "submit", - "style": "btn-default", - "title": "OK" - } - ] -} diff --git a/examples/data/titlemaps.json b/examples/data/titlemaps.json deleted file mode 100644 index e56cc5f38..000000000 --- a/examples/data/titlemaps.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "model": { - "select": "a", - "array": ["b"] - }, - "schema": { - "type": "object", - "properties": { - "select": { - "title": "Select without titleMap", - "type": "string", - "enum": ["a","b","c"] - }, - "select2": { - "title": "Select with titleMap (old style)", - "type": "string", - "enum": ["a","b","c"] - }, - "noenum": { "type": "string", "title": "No enum, but forms says it's a select" }, - "array": { - "title": "Array with enum defaults to 'checkboxes'", - "type": "array", - "items": { - "type": "string", - "enum": ["a","b","c"] - } - }, - "array2": { - "title": "Array with titleMap", - "type": "array", - "default": ["b","c"], - "items": { - "type": "string", - "enum": ["a","b","c"] - } - }, - "radios": { - "title": "Basic radio button example", - "type": "string", - "enum": ["a","b","c"] - }, - "radiobuttons": { - "title": "Radio buttons used to switch a boolean", - "type": "boolean", - "default": false - } - } - }, - "form": [ - "select", - { - "key": "select2", - "type": "select", - "titleMap": { - "a": "A", - "b": "B", - "c": "C" - } - }, - { - "key": "noenum", - "type": "select", - "titleMap": [ - { "value":"a", "name": "A" }, - { "value":"b", "name":"B" }, - { "value":"c", "name":"C" } - ] - }, - "array", - { - "key": "array2", - "type": "checkboxes", - "titleMap": [ - { "value":"a", "name": "A" }, - { "value":"b", "name":"B" }, - { "value":"c", "name":"C" } - ] - }, - { - "key": "radios", - "type": "radios", - "titleMap": [ - { "value":"c", "name": "C" }, - { "value":"b", "name":"B" }, - { "value":"a", "name":"A" } - ] - }, - { - "key":"radiobuttons", - "type": "radiobuttons", - "titleMap": [ - {"value": false, "name": "No way"}, - {"value": true, "name": "OK"} - ] - } - ] -} diff --git a/examples/data/types.json b/examples/data/types.json deleted file mode 100644 index ebe3c4938..000000000 --- a/examples/data/types.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "schema": { - "type": "object", - "title": "Types", - "properties": { - "string": { - "type": "string", - "minLength": 3 - }, - "integer": { - "type": "integer" - }, - "number": { - "type": "number" - }, - "boolean": { - "type": "boolean" - } - }, - "required": ["number"] - }, - "form": [ - "*", - {"type": "submit", "title": "OK"} - ] -} diff --git a/gulp/tasks/bootstrap-datepicker.js b/gulp/tasks/bootstrap-datepicker.js deleted file mode 100644 index c3b066e83..000000000 --- a/gulp/tasks/bootstrap-datepicker.js +++ /dev/null @@ -1,29 +0,0 @@ -var gulp = require('gulp'), - streamqueue = require('streamqueue'), - minifyHtml = require('gulp-minify-html'), - templateCache = require('gulp-angular-templatecache'), - concat = require('gulp-concat'), - uglify = require('gulp-uglify'); - -gulp.task('bootstrap-datepicker', function() { - var stream = streamqueue({objectMode: true}); - stream.queue( - gulp.src('./src/directives/decorators/bootstrap/datepicker/*.html') - .pipe(minifyHtml({ - empty: true, - spare: true, - quotes: true - })) - .pipe(templateCache({ - module: 'schemaForm', - root: 'directives/decorators/bootstrap/datepicker/' - })) - ); - stream.queue(gulp.src('./src/directives/decorators/bootstrap/datepicker/*.js')); - - stream.done() - .pipe(concat('bootstrap-datepicker.min.js')) - .pipe(uglify()) - .pipe(gulp.dest('./dist/')); - -}); \ No newline at end of file diff --git a/gulp/tasks/bootstrap.js b/gulp/tasks/bootstrap.js deleted file mode 100644 index d401239d6..000000000 --- a/gulp/tasks/bootstrap.js +++ /dev/null @@ -1,42 +0,0 @@ -var gulp = require('gulp'), - streamqueue = require('streamqueue'), - minifyHtml = require('gulp-minify-html'), - templateCache = require('gulp-angular-templatecache'), - concat = require('gulp-concat'), - rename = require('gulp-rename'), - umd = require('gulp-umd'), - uglify = require('gulp-uglify'); - -gulp.task('bootstrap', function() { - var stream = streamqueue({objectMode: true}); - stream.queue( - gulp.src('./src/directives/decorators/bootstrap/*.html') - .pipe(minifyHtml({ - empty: true, - spare: true, - quotes: true - })) - .pipe(templateCache({ - module: 'schemaForm', - root: 'directives/decorators/bootstrap/' - })) - ); - stream.queue(gulp.src('./src/directives/decorators/bootstrap/*.js')); - - stream.done() - .pipe(concat('bootstrap-decorator.js')) - .pipe(umd({ - dependencies: function() { - return [ - {name: 'schemaForm'}, - ]; - }, - exports: function() {return 'schemaForm';}, - namespace: function() {return 'bootstrapDecorator';} - })) - .pipe(gulp.dest('./dist/')) - .pipe(uglify()) - .pipe(rename('bootstrap-decorator.min.js')) - .pipe(gulp.dest('./dist/')); - -}); diff --git a/gulp/tasks/default.js b/gulp/tasks/default.js index b5ee304c8..8ea9789d3 100644 --- a/gulp/tasks/default.js +++ b/gulp/tasks/default.js @@ -1,7 +1,5 @@ var gulp = require('gulp'); gulp.task('default', [ - 'minify', - 'bootstrap', - 'bootstrap-datepicker' + 'minify' ]); diff --git a/gulp/tasks/jscs.js b/gulp/tasks/jscs.js index 8d1bfef9a..4f6992f6e 100644 --- a/gulp/tasks/jscs.js +++ b/gulp/tasks/jscs.js @@ -1,5 +1,5 @@ -var gulp = require('gulp'), - jscs = require('gulp-jscs'); +var gulp = require('gulp'); +var jscs = require('gulp-jscs'); gulp.task('jscs', function() { gulp.src('./src/**/*.js') diff --git a/gulp/tasks/minify.js b/gulp/tasks/minify.js index 9cc164ef6..cca63e90e 100644 --- a/gulp/tasks/minify.js +++ b/gulp/tasks/minify.js @@ -1,30 +1,30 @@ -var gulp = require('gulp'), - concat = require('gulp-concat'), - rename = require('gulp-rename'), - umd = require('gulp-umd'), - uglify = require('gulp-uglify'); +var gulp = require('gulp'); +var concat = require('gulp-concat'); +var rename = require('gulp-rename'); +var umd = require('gulp-umd'); +var uglify = require('gulp-uglify'); gulp.task('minify', function() { gulp.src([ - './src/module.js', - './src/sfPath.js', - './src/services/*.js', - './src/directives/*.js' + './src/module.js', + './src/sfPath.js', + './src/services/*.js', + './src/directives/*.js' ]) - .pipe(concat('schema-form.js')) - .pipe(umd({ - dependencies: function() { - return [ - {name: 'angular'}, - {name: 'objectpath'}, - {name: 'tv4'}, - ]; - }, - exports: function() {return 'schemaForm';}, - namespace: function() {return 'schemaForm';} + .pipe(concat('schema-form.js')) + .pipe(umd({ + dependencies: function() { + return [ + {name: 'angular'}, + {name: 'objectpath'}, + {name: 'tv4'}, + ]; + }, + exports: function() {return 'schemaForm';}, + namespace: function() {return 'schemaForm';} })) - .pipe(gulp.dest('./dist/')) - .pipe(uglify()) - .pipe(rename('schema-form.min.js')) - .pipe(gulp.dest('./dist/')); + .pipe(gulp.dest('./dist/')) + .pipe(uglify()) + .pipe(rename('schema-form.min.js')) + .pipe(gulp.dest('./dist/')); }); diff --git a/gulp/tasks/protractor.js b/gulp/tasks/protractor.js deleted file mode 100644 index 9a3bb1864..000000000 --- a/gulp/tasks/protractor.js +++ /dev/null @@ -1,38 +0,0 @@ -var gulp = require('gulp'); - -// The protractor task -var protractor = require('gulp-protractor'); - -// Start a standalone server -var webdriver_standalone = protractor.webdriver_standalone; - -// Download and update the selenium driver -var webdriver_update = protractor.webdriver_update; - -// Downloads the selenium webdriver -gulp.task('webdriver-update', webdriver_update); - -// Start the standalone selenium server -// NOTE: This is not needed if you reference the -// seleniumServerJar in your protractor.conf.js -gulp.task('webdriver-standalone', webdriver_standalone); - - -// Setting up the test task -gulp.task('protractor', ['webdriver-update'], function(cb) { - gulp.src(['test/protractor/specs/**/*.js']).pipe(protractor.protractor({ - configFile: 'test/protractor/conf.js', - })).on('error', function(e) { - console.log(e); - }).on('end', cb); -}); - -['validation-messages', 'custom-validation'].forEach(function(name) { - gulp.task('protractor:' + name, ['webdriver-update'], function(cb) { - gulp.src(['test/protractor/specs/' + name + '.js']).pipe(protractor.protractor({ - configFile: 'test/protractor/conf.js', - })).on('error', function(e) { - console.log(e); - }).on('end', cb); - }); -}); diff --git a/karma.conf.js b/karma.conf.js index 729a95568..40080b914 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -23,8 +23,7 @@ module.exports = function(config) { 'src/sfPath.js', 'src/services/*.js', 'src/directives/*.js', - 'src/directives/decorators/bootstrap/*.js', - 'src/**/*.html', + 'bower_components/angular-schema-form-bootstrap/bootstrap-decorator.js', 'test/services/schema-form-test.js', 'test/services/decorators-test.js', 'test/services/messages-test.js', @@ -43,8 +42,7 @@ module.exports = function(config) { reporters: ['progress','coverage','growler'], preprocessors: { - 'src/**/*.js': ['coverage'], - 'src/**/*.html': ['ng-html2js'] + 'src/**/*.js': ['coverage'] }, // optionally, configure the reporter @@ -53,15 +51,6 @@ module.exports = function(config) { dir : 'coverage/' }, - - ngHtml2JsPreprocessor: { - cacheIdFromPath: function(filepath) { - return filepath.substr(4); - }, - moduleName: 'templates' - }, - - // web server port port: 9876, diff --git a/package.json b/package.json index 85ebdd8e0..6f5df7a37 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-schema-form", - "version": "0.8.12", + "version": "0.8.13", "description": "Create complex forms from a JSON schema with angular.", "repository": "Textalk/angular-schema-form", "main": "dist/schema-form.min.js", @@ -14,7 +14,8 @@ "David Jensen (https://github.com/davidlgj)", "Denis Dervisevic (https://github.com/Dervisevic)", "Cameron Edwards (https://github.com/cameronprattedwards)", - "Mike Marcacci (https://github.com/mike-marcacci)" + "Mike Marcacci (https://github.com/mike-marcacci)", + "Marcel Bennett (https://github.com/Anthropic)" ], "license": "MIT", "dependencies": { @@ -29,6 +30,8 @@ "form", "json", "json-schema", + "json-schema-form", + "ui-schema", "schema" ], "devDependencies": { @@ -48,7 +51,6 @@ "karma-coverage": "^0.2.1", "karma-growler-reporter": "0.0.1", "karma-mocha": "^0.1.3", - "karma-ng-html2js-preprocessor": "^0.1.0", "karma-phantomjs-launcher": "^0.1.4", "mocha": "^1.18.0", "mocha-lcov-reporter": "0.0.1", @@ -69,9 +71,7 @@ "basePath": "/dist/", "files": [ "schema-form.min.js", - "schema-form.js", - "bootstrap-decorator.min.js", - "bootstrap-decorator.js" + "schema-form.js" ] } } diff --git a/src/directives/WHERE_ARE_THE_DECORATORS.md b/src/directives/WHERE_ARE_THE_DECORATORS.md new file mode 100644 index 000000000..67fb9d32f --- /dev/null +++ b/src/directives/WHERE_ARE_THE_DECORATORS.md @@ -0,0 +1,7 @@ +Hi there, + +as you probably have noticed the `decorators` folder and the bootstrap decorator has +been removed. This is intentional. Each decorator has it's own repo. + +Material: https://github.com/Textalk/angular-schema-form-material +Bootstrap: https://github.com/Textalk/angular-schema-form-bootstrap diff --git a/src/directives/decorators/bootstrap/actions-trcl.html b/src/directives/decorators/bootstrap/actions-trcl.html deleted file mode 100644 index 0b32cbc2f..000000000 --- a/src/directives/decorators/bootstrap/actions-trcl.html +++ /dev/null @@ -1 +0,0 @@ -
diff --git a/src/directives/decorators/bootstrap/actions.html b/src/directives/decorators/bootstrap/actions.html deleted file mode 100644 index ddf0789e1..000000000 --- a/src/directives/decorators/bootstrap/actions.html +++ /dev/null @@ -1,13 +0,0 @@ -
- - -
diff --git a/src/directives/decorators/bootstrap/array.html b/src/directives/decorators/bootstrap/array.html deleted file mode 100644 index f46838639..000000000 --- a/src/directives/decorators/bootstrap/array.html +++ /dev/null @@ -1,28 +0,0 @@ -
- -
    -
  1. - - -
  2. -
-
- -
-
-
diff --git a/src/directives/decorators/bootstrap/bootstrap-decorator.js b/src/directives/decorators/bootstrap/bootstrap-decorator.js deleted file mode 100644 index 4f0d5bb7e..000000000 --- a/src/directives/decorators/bootstrap/bootstrap-decorator.js +++ /dev/null @@ -1,60 +0,0 @@ -angular.module('schemaForm').config(['schemaFormDecoratorsProvider', function(decoratorsProvider) { - var base = 'directives/decorators/bootstrap/'; - - decoratorsProvider.defineDecorator('bootstrapDecorator', { - textarea: {template: base + 'textarea.html', replace: false}, - fieldset: {template: base + 'fieldset.html', replace: false}, - /*fieldset: {template: base + 'fieldset.html', replace: true, builder: function(args) { - var children = args.build(args.form.items, args.path + '.items'); - console.log('fieldset children frag', children.childNodes) - args.fieldFrag.childNode.appendChild(children); - }},*/ - array: {template: base + 'array.html', replace: false}, - tabarray: {template: base + 'tabarray.html', replace: false}, - tabs: {template: base + 'tabs.html', replace: false}, - section: {template: base + 'section.html', replace: false}, - conditional: {template: base + 'section.html', replace: false}, - actions: {template: base + 'actions.html', replace: false}, - select: {template: base + 'select.html', replace: false}, - checkbox: {template: base + 'checkbox.html', replace: false}, - checkboxes: {template: base + 'checkboxes.html', replace: false}, - number: {template: base + 'default.html', replace: false}, - password: {template: base + 'default.html', replace: false}, - submit: {template: base + 'submit.html', replace: false}, - button: {template: base + 'submit.html', replace: false}, - radios: {template: base + 'radios.html', replace: false}, - 'radios-inline': {template: base + 'radios-inline.html', replace: false}, - radiobuttons: {template: base + 'radio-buttons.html', replace: false}, - help: {template: base + 'help.html', replace: false}, - 'default': {template: base + 'default.html', replace: false} - }, []); - - //manual use directives - decoratorsProvider.createDirectives({ - textarea: base + 'textarea.html', - select: base + 'select.html', - checkbox: base + 'checkbox.html', - checkboxes: base + 'checkboxes.html', - number: base + 'default.html', - submit: base + 'submit.html', - button: base + 'submit.html', - text: base + 'default.html', - date: base + 'default.html', - password: base + 'default.html', - datepicker: base + 'datepicker.html', - input: base + 'default.html', - radios: base + 'radios.html', - 'radios-inline': base + 'radios-inline.html', - radiobuttons: base + 'radio-buttons.html', - }); - -}]).directive('sfFieldset', function() { - return { - transclude: true, - scope: true, - templateUrl: 'directives/decorators/bootstrap/fieldset-trcl.html', - link: function(scope, element, attrs) { - scope.title = scope.$eval(attrs.title); - } - }; -}); diff --git a/src/directives/decorators/bootstrap/checkbox.html b/src/directives/decorators/bootstrap/checkbox.html deleted file mode 100644 index d6ad64d4b..000000000 --- a/src/directives/decorators/bootstrap/checkbox.html +++ /dev/null @@ -1,15 +0,0 @@ -
- -
-
diff --git a/src/directives/decorators/bootstrap/checkboxes.html b/src/directives/decorators/bootstrap/checkboxes.html deleted file mode 100644 index 45b514135..000000000 --- a/src/directives/decorators/bootstrap/checkboxes.html +++ /dev/null @@ -1,18 +0,0 @@ -
- -
- - -
-
-
diff --git a/src/directives/decorators/bootstrap/default.html b/src/directives/decorators/bootstrap/default.html deleted file mode 100644 index b5685042b..000000000 --- a/src/directives/decorators/bootstrap/default.html +++ /dev/null @@ -1,54 +0,0 @@ -
- - - - -
- - - - -
- - - - {{ hasSuccess() ? '(success)' : '(error)' }} - -
-
diff --git a/src/directives/decorators/bootstrap/fieldset-trcl.html b/src/directives/decorators/bootstrap/fieldset-trcl.html deleted file mode 100644 index e4069bd77..000000000 --- a/src/directives/decorators/bootstrap/fieldset-trcl.html +++ /dev/null @@ -1,5 +0,0 @@ -
- {{ form.title }} -
-
-
diff --git a/src/directives/decorators/bootstrap/fieldset.html b/src/directives/decorators/bootstrap/fieldset.html deleted file mode 100644 index 4db3f059b..000000000 --- a/src/directives/decorators/bootstrap/fieldset.html +++ /dev/null @@ -1,5 +0,0 @@ -
- {{ form.title }} -
- -
diff --git a/src/directives/decorators/bootstrap/help.html b/src/directives/decorators/bootstrap/help.html deleted file mode 100644 index aca5bb56a..000000000 --- a/src/directives/decorators/bootstrap/help.html +++ /dev/null @@ -1 +0,0 @@ -
diff --git a/src/directives/decorators/bootstrap/radio-buttons.html b/src/directives/decorators/bootstrap/radio-buttons.html deleted file mode 100644 index 5a12dc86a..000000000 --- a/src/directives/decorators/bootstrap/radio-buttons.html +++ /dev/null @@ -1,24 +0,0 @@ -
-
- -
-
- -
-
-
diff --git a/src/directives/decorators/bootstrap/radios-inline.html b/src/directives/decorators/bootstrap/radios-inline.html deleted file mode 100644 index 174a1587a..000000000 --- a/src/directives/decorators/bootstrap/radios-inline.html +++ /dev/null @@ -1,20 +0,0 @@ -
- -
- -
-
-
diff --git a/src/directives/decorators/bootstrap/radios.html b/src/directives/decorators/bootstrap/radios.html deleted file mode 100644 index 49f84ee8b..000000000 --- a/src/directives/decorators/bootstrap/radios.html +++ /dev/null @@ -1,20 +0,0 @@ -
- -
- -
-
-
diff --git a/src/directives/decorators/bootstrap/section.html b/src/directives/decorators/bootstrap/section.html deleted file mode 100644 index 57224a290..000000000 --- a/src/directives/decorators/bootstrap/section.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/src/directives/decorators/bootstrap/select.html b/src/directives/decorators/bootstrap/select.html deleted file mode 100644 index b4b3296e0..000000000 --- a/src/directives/decorators/bootstrap/select.html +++ /dev/null @@ -1,16 +0,0 @@ -
- - -
-
diff --git a/src/directives/decorators/bootstrap/submit.html b/src/directives/decorators/bootstrap/submit.html deleted file mode 100644 index f2b666195..000000000 --- a/src/directives/decorators/bootstrap/submit.html +++ /dev/null @@ -1,15 +0,0 @@ -
- - -
diff --git a/src/directives/decorators/bootstrap/tabarray.html b/src/directives/decorators/bootstrap/tabarray.html deleted file mode 100644 index a6ee56f2a..000000000 --- a/src/directives/decorators/bootstrap/tabarray.html +++ /dev/null @@ -1,58 +0,0 @@ - - diff --git a/src/directives/decorators/bootstrap/tabs.html b/src/directives/decorators/bootstrap/tabs.html deleted file mode 100644 index 747a358bd..000000000 --- a/src/directives/decorators/bootstrap/tabs.html +++ /dev/null @@ -1,21 +0,0 @@ -
- - -
-
- - -
-
-
diff --git a/src/directives/decorators/bootstrap/textarea.html b/src/directives/decorators/bootstrap/textarea.html deleted file mode 100644 index 06364edfc..000000000 --- a/src/directives/decorators/bootstrap/textarea.html +++ /dev/null @@ -1,35 +0,0 @@ -
- - - - -
- - - -
- - -
diff --git a/src/directives/field.js b/src/directives/field.js index 556f30c59..66147d3f2 100644 --- a/src/directives/field.js +++ b/src/directives/field.js @@ -188,6 +188,9 @@ angular.module('schemaForm').directive('sfField', scope.ngModel.$setValidity(error, validity === true); if (validity === true) { + // Re-trigger model validator, that model itself would be re-validated + scope.ngModel.$validate(); + // Setting or removing a validity can change the field to believe its valid // but its not. So lets trigger its validation as well. scope.$broadcast('schemaFormValidate'); diff --git a/src/directives/schema-form.js b/src/directives/schema-form.js index 53d473939..1b21885e4 100644 --- a/src/directives/schema-form.js +++ b/src/directives/schema-form.js @@ -170,7 +170,7 @@ angular.module('schemaForm') // part of the form or schema is chnaged without it being a new instance. scope.$on('schemaFormRedraw', function() { var schema = scope.schema; - var form = scope.initialForm || ['*']; + var form = scope.initialForm ? angular.copy(scope.initialForm) : ['*']; if (schema) { render(schema, form); } diff --git a/src/services/decorators.js b/src/services/decorators.js index bd13a94fc..1aa0987bd 100644 --- a/src/services/decorators.js +++ b/src/services/decorators.js @@ -256,6 +256,9 @@ angular.module('schemaForm').provider('schemaFormDecorators', scope.ngModel.$setValidity(error, validity === true); if (validity === true) { + // Re-trigger model validator, that model itself would be re-validated + scope.ngModel.$validate(); + // Setting or removing a validity can change the field to believe its valid // but its not. So lets trigger its validation as well. scope.$broadcast('schemaFormValidate'); diff --git a/test/directives/schema-form-test.js b/test/directives/schema-form-test.js index 3f3aaafc7..a34553c86 100644 --- a/test/directives/schema-form-test.js +++ b/test/directives/schema-form-test.js @@ -1,7 +1,6 @@ chai.should(); describe('directive',function(){ - beforeEach(module('templates')); beforeEach(module('schemaForm')); beforeEach( //We don't need no sanitation. We don't need no thought control. @@ -46,12 +45,11 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.equal(2); - tmpl.children().eq(0).is('bootstrap-decorator').should.be.true; - tmpl.children().eq(0).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').is('input[type="text"]').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').attr('ng-model').should.be.equal('model[\'name\']'); - tmpl.children().eq(1).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(1).children().eq(0).children('select').length.should.equal(1); + tmpl.children().eq(0).is('div.form-group').should.be.true; + tmpl.children().eq(0).find('input').is('input[type="text"]').should.be.true; + tmpl.children().eq(0).find('input').attr('ng-model').should.be.equal('model[\'name\']'); + tmpl.children().eq(0).is('div.form-group').should.be.true; + tmpl.children().eq(1).children('select').length.should.equal(1); }); }); @@ -72,12 +70,11 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.equal(2); - tmpl.children().eq(0).is('bootstrap-decorator').should.be.true; - tmpl.children().eq(0).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').is('input[type="text"]').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').attr('ng-model').should.be.equal('model[\'name\']'); - tmpl.children().eq(1).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(1).children().eq(0).children('select').length.should.equal(1); + tmpl.children().eq(0).is('div.form-group').should.be.true; + tmpl.children().eq(0).find('input').is('input[type="text"]').should.be.true; + tmpl.children().eq(0).find('input').attr('ng-model').should.be.equal('model[\'name\']'); + tmpl.children().eq(1).is('div.form-group').should.be.true; + tmpl.children().eq(1).children('select').length.should.equal(1); }); }); @@ -98,11 +95,11 @@ describe('directive',function(){ }, 'ianal': { 'type': 'boolean', - 'title':'IANAL' + 'title': 'IANAL' }, 'age': { 'type': 'integer', - 'title':'Age', + 'title': 'Age', 'minimum': 0 }, 'sum': { @@ -147,30 +144,30 @@ describe('directive',function(){ tmpl.children().length.should.be.equal(6); - tmpl.children().eq(0).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').is('input[type="text"]').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').attr('ng-model').should.be.equal('model[\'name\']'); + tmpl.children().eq(0).is('div.form-group').should.be.true; + tmpl.children().eq(0).find('input').is('input[type="text"]').should.be.true; + tmpl.children().eq(0).find('input').attr('ng-model').should.be.equal('model[\'name\']'); + + tmpl.children().eq(1).is('div.checkbox').should.be.true; + tmpl.children().eq(1).find('input[type="checkbox"]').length.should.be.eq(1); - tmpl.children().eq(1).children().eq(0).is('div.checkbox').should.be.true; - tmpl.children().eq(1).children().eq(0).find('input[type="checkbox"]').length.should.be.eq(1); + tmpl.children().eq(2).is('div.form-group').should.be.true; + tmpl.children().eq(2).find('input[type="number"]').length.should.be.eq(1); - tmpl.children().eq(2).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(2).children().eq(0).find('input[type="number"]').length.should.be.eq(1); + tmpl.children().eq(3).is('div.form-group').should.be.true; + tmpl.children().eq(3).find('input[type="number"]').length.should.be.eq(1); - tmpl.children().eq(3).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(3).children().eq(0).find('input[type="number"]').length.should.be.eq(1); + tmpl.children().eq(4).is('div.form-group').should.be.true; + tmpl.children().eq(4).find('select').length.should.be.eq(1); - tmpl.children().eq(4).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(4).children().eq(0).find('select').length.should.be.eq(1); + tmpl.children().eq(5).is('fieldset').should.be.true; + tmpl.children().eq(5).children().eq(0).is('legend').should.be.true; - tmpl.children().eq(5).children().eq(0).is('fieldset').should.be.true; - tmpl.children().eq(5).children().eq(0).children().eq(0).is('legend').should.be.true; - tmpl.children().eq(5).children().eq(0).children().eq(3).is('sf-decorator').should.be.true; + tmpl.children().eq(5).children().eq(4).is('fieldset').should.be.true; + tmpl.children().eq(5).children().eq(4).children().length.should.be.eq(4); - tmpl.children().eq(5).children().eq(0).children().eq(4).children().eq(0).is('fieldset').should.be.true; - tmpl.children().eq(5).children().eq(0).children().eq(4).children().eq(0).children().length.should.be.eq(4); - tmpl.children().eq(5).children().eq(0).children().eq(4).children().eq(0).find('input[ng-model="model[\'attributes\'][\'shoulders\'][\'left\']"]').length.should.be.eq(1); - tmpl.children().eq(5).children().eq(0).children().eq(4).children().eq(0).find('input[ng-model="model[\'attributes\'][\'shoulders\'][\'right\']"]').length.should.be.eq(1); + tmpl.children().eq(5).children().eq(4).find('input[ng-model="model[\'attributes\'][\'shoulders\'][\'left\']"]').length.should.be.eq(1); + tmpl.children().eq(5).children().eq(4).find('input[ng-model="model[\'attributes\'][\'shoulders\'][\'right\']"]').length.should.be.eq(1); }); }); @@ -189,12 +186,12 @@ describe('directive',function(){ $compile(tmpl)(scope); $rootScope.$apply(); + tmpl.children().length.should.be.equal(2); tmpl.children().eq(0).is('input[type="text"]').should.be.true; tmpl.children().eq(0).attr('ng-model').should.be.equal('person.name'); - tmpl.children().eq(1).is('bootstrap-decorator').should.be.true; - tmpl.children().eq(1).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(1).children().eq(0).children('select').length.should.equal(1); + tmpl.children().eq(1).is('div.form-group').should.be.true; + tmpl.children().eq(1).children('select').length.should.equal(1); }); }); @@ -246,12 +243,12 @@ describe('directive',function(){ $compile(tmpl)(scope); $rootScope.$apply(); tmpl.children().length.should.be.equal(3); - tmpl.children().eq(0).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').is('input[type="text"]').should.be.true; - tmpl.children().eq(1).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(1).children().eq(0).children('select').length.should.equal(1); - tmpl.children().eq(2).children().eq(0).find('input').is('input[type=submit]').should.be.true; - tmpl.children().eq(2).children().eq(0).find('input').val().should.be.equal('Okidoki'); + tmpl.children().eq(0).is('div.form-group').should.be.true; + tmpl.children().eq(0).find('input').is('input[type="text"]').should.be.true; + tmpl.children().eq(1).is('div.form-group').should.be.true; + tmpl.children().eq(1).children('select').length.should.equal(1); + tmpl.children().eq(2).find('input').is('input[type=submit]').should.be.true; + tmpl.children().eq(2).find('input').val().should.be.equal('Okidoki'); }); }); @@ -271,12 +268,12 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.equal(3); - tmpl.children().eq(0).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').is('input[type="text"]').should.be.true; - tmpl.children().eq(1).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(1).children().eq(0).children('select').length.should.equal(1); - tmpl.children().eq(2).children().eq(0).find('button').length.should.be.equal(1); - tmpl.children().eq(2).children().eq(0).find('button').text().should.include('Okidoki'); + tmpl.children().eq(0).is('div.form-group').should.be.true; + tmpl.children().eq(0).find('input').is('input[type="text"]').should.be.true; + tmpl.children().eq(1).is('div.form-group').should.be.true; + tmpl.children().eq(1).children('select').length.should.equal(1); + tmpl.children().eq(2).find('button').length.should.be.equal(1); + tmpl.children().eq(2).find('button').text().should.include('Okidoki'); scope.form[1].onClick.should.not.have.beenCalled; tmpl.children().eq(2).children().eq(0).find('button').click(); @@ -356,12 +353,12 @@ describe('directive',function(){ $compile(tmpl)(scope); $rootScope.$apply(); tmpl.children().length.should.be.equal(2); - tmpl.children().eq(0).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').is('input[type="text"]').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').attr('disabled').should.be.equal('disabled'); - tmpl.children().eq(1).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(1).children().eq(0).children('input').length.should.equal(1); - expect(tmpl.children().eq(1).children().eq(0).children('input').attr('disabled')).to.be.undefined; + tmpl.children().eq(0).is('div.form-group').should.be.true; + tmpl.children().eq(0).find('input').is('input[type="text"]').should.be.true; + tmpl.children().eq(0).find('input').attr('disabled').should.be.equal('disabled'); + tmpl.children().eq(1).is('div.form-group').should.be.true; + tmpl.children().eq(1).children('input').length.should.equal(1); + expect(tmpl.children().eq(1).children('input').attr('disabled')).to.be.undefined; }); }); @@ -387,12 +384,12 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.equal(2); - tmpl.children().eq(0).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').is('input[type="text"]').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').attr('disabled').should.be.equal('disabled'); - tmpl.children().eq(1).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(1).children().eq(0).children('input').length.should.equal(1); - expect(tmpl.children().eq(1).children().eq(0).children('input').attr('disabled')).to.be.undefined; + tmpl.children().eq(0).is('div.form-group').should.be.true; + tmpl.children().eq(0).find('input').is('input[type="text"]').should.be.true; + tmpl.children().eq(0).find('input').attr('disabled').should.be.equal('disabled'); + tmpl.children().eq(1).is('div.form-group').should.be.true; + tmpl.children().eq(1).children('input').length.should.equal(1); + expect(tmpl.children().eq(1).children('input').attr('disabled')).to.be.undefined; }); }); @@ -421,12 +418,12 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.equal(2); - tmpl.children().eq(0).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').is('input[type="text"]').should.be.true; - tmpl.children().eq(0).children().eq(0).find('input').attr('disabled').should.be.equal('disabled'); - tmpl.children().eq(1).children().eq(0).is('div.form-group').should.be.true; - tmpl.children().eq(1).children().eq(0).children('input').length.should.equal(1); - expect(tmpl.children().eq(1).children().eq(0).children('input').attr('disabled')).to.be.undefined; + tmpl.children().eq(0).is('div.form-group').should.be.true; + tmpl.children().eq(0).find('input').is('input[type="text"]').should.be.true; + tmpl.children().eq(0).find('input').attr('disabled').should.be.equal('disabled'); + tmpl.children().eq(1).is('div.form-group').should.be.true; + tmpl.children().eq(1).children('input').length.should.equal(1); + expect(tmpl.children().eq(1).children('input').attr('disabled')).to.be.undefined; }); }); @@ -668,7 +665,7 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.eq(1); - var labels = tmpl.children().children().find('label'); + var labels = tmpl.children().find('label'); labels.eq(0).text().should.equal('Nick'); labels.eq(1).text().should.equal('Name'); labels.eq(2).text().should.equal('Alias'); @@ -718,7 +715,6 @@ describe('directive',function(){ $compile(tmpl)(scope); $rootScope.$apply(); - scope.person.arr[0].name.should.be.equal('Name'); scope.person.arr[0].nick.should.be.equal('Nick'); expect(scope.person.arr[0].alias).to.be.undefined; @@ -748,8 +744,8 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.equal(2); - tmpl.children().eq(0).children().eq(0).find('label').hasClass('sr-only').should.be.true; - tmpl.children().eq(1).children().eq(0).find('label').hasClass('ng-hide').should.be.true; + tmpl.children().eq(0).eq(0).find('label').hasClass('sr-only').should.be.true; + tmpl.children().eq(1).eq(0).find('label').hasClass('ng-hide').should.be.true; }); }); @@ -783,7 +779,7 @@ describe('directive',function(){ //TODO: more asserts tmpl.children().length.should.be.equal(2); - tmpl.children().eq(0).children().eq(0).find('input[type=checkbox]').length.should.be.eq(2); + tmpl.children().eq(0).find('input[type=checkbox]').length.should.be.eq(2); }); }); @@ -853,10 +849,10 @@ describe('directive',function(){ //TODO: more asserts tmpl.children().length.should.be.equal(2); - tmpl.children().eq(0).children().eq(0).find('input[type=radio]').length.should.be.eq(2); - tmpl.children().eq(0).children().eq(0).find('.radio').length.should.be.eq(2); - tmpl.children().eq(1).children().eq(0).find('input[type=radio]').length.should.be.eq(2); - tmpl.children().eq(1).children().eq(0).find('.btn').length.should.be.eq(2); + tmpl.children().eq(0).find('input[type=radio]').length.should.be.eq(2); + tmpl.children().eq(0).find('.radio').length.should.be.eq(2); + tmpl.children().eq(1).find('input[type=radio]').length.should.be.eq(2); + tmpl.children().eq(1).find('.btn').length.should.be.eq(2); }); }); @@ -1083,54 +1079,9 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.equal(1); - tmpl.children().eq(0).children().eq(0).is('div').should.be.true; - tmpl.children().eq(0).children().eq(0).hasClass('btn-group').should.be.false; - tmpl.children().eq(0).children().eq(0).children().length.should.be.eq(2); - }); - }); - - it('should handle a simple div with a condition if "conditional" is specified',function(done){ - - inject(function($compile,$rootScope){ - var scope = $rootScope.$new(); - scope.person = { show: false }; - - scope.schema = exampleSchema; - - scope.form = [{ - type: "conditional", - condition: "person.show", - items: [ - { - key: 'name', - notitle: true - }, - { - key: 'gender', - notitle: true - } - ] - }]; - - var tmpl = angular.element('
'); - - $compile(tmpl)(scope); - $rootScope.$apply(); - - tmpl.children().length.should.be.equal(1); - tmpl.children().eq(0).children().length.should.be.equal(0); - - //Do a setTimeout so we kan do another $apply - setTimeout(function(){ - scope.person.show = true; - scope.$apply(); - tmpl.children().length.should.be.equal(1); - tmpl.children().eq(0).children().eq(0).is('div').should.be.true; - tmpl.children().eq(0).children().eq(0).hasClass('btn-group').should.be.false; - tmpl.children().eq(0).children().eq(0).children().length.should.be.eq(2); - done(); - },10); - + tmpl.children().eq(0).is('div').should.be.true; + tmpl.children().eq(0).hasClass('btn-group').should.be.false; + tmpl.children().eq(0).children().length.should.be.eq(2); }); }); @@ -1162,11 +1113,11 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.equal(1); - tmpl.children().eq(0).children().eq(0).is('div').should.be.true; - tmpl.children().eq(0).children().eq(0).hasClass('btn-group').should.be.true; - tmpl.children().eq(0).children().eq(0).children().length.should.be.eq(2); - tmpl.children().eq(0).children().eq(0).children().eq(0).is('input').should.be.true; - tmpl.children().eq(0).children().eq(0).children().eq(1).is('button').should.be.true; + tmpl.children().eq(0).is('div').should.be.true; + tmpl.children().eq(0).hasClass('btn-group').should.be.true; + tmpl.children().eq(0).children().length.should.be.eq(2); + tmpl.children().eq(0).children().eq(0).is('input').should.be.true; + tmpl.children().eq(0).children().eq(1).is('button').should.be.true; }); }); @@ -1208,14 +1159,13 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.be.equal(1); - tmpl.children().eq(0).children().length.should.be.equal(1); - tmpl.children().eq(0).children().eq(0).children().length.should.be.eq(4); - tmpl.children().eq(0).children().eq(0).children().eq(0).hasClass('btn-success').should.be.false; - tmpl.children().eq(0).children().eq(0).children().eq(1).hasClass('btn-default').should.be.true; - tmpl.children().eq(0).children().eq(0).children().eq(1).hasClass('btn-danger').should.be.false; - tmpl.children().eq(0).children().eq(0).children().eq(2).hasClass('btn-success').should.be.true; - tmpl.children().eq(0).children().eq(0).children().eq(3).hasClass('btn-default').should.be.false; - tmpl.children().eq(0).children().eq(0).children().eq(3).hasClass('btn-danger').should.be.true; + tmpl.children().eq(0).children().length.should.be.eq(4); + tmpl.children().eq(0).children().eq(0).hasClass('btn-success').should.be.false; + tmpl.children().eq(0).children().eq(1).hasClass('btn-default').should.be.true; + tmpl.children().eq(0).children().eq(1).hasClass('btn-danger').should.be.false; + tmpl.children().eq(0).children().eq(2).hasClass('btn-success').should.be.true; + tmpl.children().eq(0).children().eq(3).hasClass('btn-default').should.be.false; + tmpl.children().eq(0).children().eq(3).hasClass('btn-danger').should.be.true; }); }); @@ -1255,9 +1205,9 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.eq(2); - tmpl.children().eq(0).children().eq(0).is('div').should.be.true; - tmpl.children().eq(0).children().eq(0).children().length.should.eq(1); - tmpl.children().eq(0).children().eq(0).children().html().should.be.eq("Yo Ninja!"); + tmpl.children().eq(0).is('div').should.be.true; + tmpl.children().eq(0).children().length.should.eq(1); + tmpl.children().eq(0).children().html().should.be.eq("Yo Ninja!"); }); }); @@ -1306,8 +1256,8 @@ describe('directive',function(){ $rootScope.$apply(); tmpl.children().length.should.eq(1); - var tabs = tmpl.children().children().eq(0).children().eq(0); - var panes = tmpl.children().children().eq(0).children().eq(1); + var tabs = tmpl.children().children().eq(0); + var panes = tmpl.children().children().eq(1); tabs.is('ul').should.be.true; tabs.children().length.should.be.eq(2); @@ -1394,19 +1344,19 @@ describe('directive',function(){ //TODO: more asserts tmpl.children().length.should.be.equal(3); - tmpl.children().eq(0).children().eq(0).find('input').length.should.be.eq(1); - tmpl.children().eq(0).children().eq(0).find('button').length.should.be.eq(2); - tmpl.children().eq(0).children().eq(0).find('button').eq(1).text().trim().should.be.eq('Add'); - - tmpl.children().eq(1).children().eq(0).find('input').length.should.be.eq(1); - tmpl.children().eq(1).children().eq(0).find('fieldset').length.should.be.eq(0); - tmpl.children().eq(1).children().eq(0).find('button').length.should.be.eq(2); - tmpl.children().eq(1).children().eq(0).find('button').eq(1).text().trim().should.be.eq('Add'); - - tmpl.children().eq(2).children().eq(0).find('input').length.should.be.eq(2); - tmpl.children().eq(2).children().eq(0).find('fieldset').length.should.be.eq(1); - tmpl.children().eq(2).children().eq(0).find('button').length.should.be.eq(4); - tmpl.children().eq(2).children().eq(0).find('button').eq(3).text().trim().should.be.eq('Add'); + tmpl.children().eq(0).find('input').length.should.be.eq(1); + tmpl.children().eq(0).find('button').length.should.be.eq(2); + tmpl.children().eq(0).find('button').eq(1).text().trim().should.be.eq('Add'); + + tmpl.children().eq(1).find('input').length.should.be.eq(1); + tmpl.children().eq(1).find('fieldset').length.should.be.eq(0); + tmpl.children().eq(1).find('button').length.should.be.eq(2); + tmpl.children().eq(1).find('button').eq(1).text().trim().should.be.eq('Add'); + + tmpl.children().eq(2).find('input').length.should.be.eq(2); + tmpl.children().eq(2).find('fieldset').length.should.be.eq(1); + tmpl.children().eq(2).find('button').length.should.be.eq(4); + tmpl.children().eq(2).find('button').eq(3).text().trim().should.be.eq('Add'); }); @@ -1546,21 +1496,17 @@ describe('directive',function(){ //TODO: more asserts tmpl.children().length.should.be.equal(2); - tmpl.children().eq(0).children().eq(0).find('input').length.should.be.eq(3); - tmpl.children().eq(0).children().eq(0).find('button').length.should.be.eq(3); - tmpl.children().eq(0).children().eq(0).find('button').eq(0).text().trim().should.be.eq('Remove'); - tmpl.children().eq(0).children().eq(0).is('div').should.be.true; - tmpl.children().eq(0).children().eq(0).attr('sf-array').should.be.thruthy; - tmpl.children().eq(0).children().eq(0).find('.tabs-left').length.should.be.eq(1); - - tmpl.children().eq(1).children().eq(0).find('input').length.should.be.eq(1); - tmpl.children().eq(1).children().eq(0).find('fieldset').length.should.be.eq(0); - tmpl.children().eq(1).children().eq(0).find('button').length.should.be.eq(1); - tmpl.children().eq(1).children().eq(0).find('button').text().trim().should.be.eq('Remove'); - tmpl.children().eq(1).children().eq(0).attr('sf-array').should.be.thruthy; - tmpl.children().eq(1).children().eq(0).find('.tabs-left').length.should.be.eq(0); - tmpl.children().eq(1).children().eq(0).find('.tabs-right').length.should.be.eq(1); - + tmpl.children().eq(0).find('input').length.should.be.eq(3); + tmpl.children().eq(0).find('button').length.should.be.eq(3); + tmpl.children().eq(0).find('button').eq(0).text().trim().should.be.eq('Remove'); + tmpl.children().eq(0).is('div').should.be.true; + tmpl.children().eq(0).find('.tabs-left').length.should.be.eq(1); + + tmpl.children().eq(1).find('input').length.should.be.eq(1); + tmpl.children().eq(1).find('fieldset').length.should.be.eq(0); + tmpl.children().eq(1).find('button').length.should.be.eq(1); + tmpl.children().eq(1).find('button').text().trim().should.be.eq('Remove'); + tmpl.children().eq(1).find('.tabs-left').length.should.be.eq(0); }); }); @@ -1628,7 +1574,7 @@ describe('directive',function(){ tmpl.children().eq(1).find('button').text().trim().should.be.eq('Delete'); tmpl.children().eq(1).find('button').eq(0).hasClass('btn-default').should.be.true; tmpl.children().eq(1).find('button').eq(0).hasClass('btn-danger').should.be.false; - tmpl.children().eq(1).find('li:not([ng-repeat]) > a').text().trim().should.be.eq('Add'); + }); }); @@ -1772,13 +1718,13 @@ describe('directive',function(){ $compile(tmpl)(scope); $rootScope.$apply(); - - tmpl.children().find('.schema-form-text').length.should.be.equal(1); + tmpl.find('.schema-form-text').length.should.be.equal(1); setTimeout(function() { + scope.person.flag = false; $rootScope.$apply(); - tmpl.children().find('.schema-form-text').length.should.be.equal(0); + tmpl.find('.schema-form-text').length.should.be.equal(0); done(); }, 0); @@ -1808,20 +1754,73 @@ describe('directive',function(){ $compile(tmpl)(scope); $rootScope.$apply(); - tmpl.children().find('.schema-form-text').length.should.be.equal(1); - tmpl.children().find('.schema-form-textarea').length.should.be.equal(0); + tmpl.find('.schema-form-text').length.should.be.equal(1); + tmpl.find('.schema-form-textarea').length.should.be.equal(0); setTimeout(function() { scope.form[0].type = 'textarea'; scope.$broadcast('schemaFormRedraw'); $rootScope.$apply(); - tmpl.children().find('.schema-form-text').length.should.be.equal(0); + tmpl.find('.schema-form-text').length.should.be.equal(0); done(); }, 0); }); }); + it('should redraw form with proper defaults on schemaFormRedraw event',function(done) { + + inject(function($compile, $rootScope){ + var scope = $rootScope.$new(); + scope.person = {}; + + scope.schema = { + type: 'object', + properties: { + name: {type: 'string'} + } + }; + + scope.form = [{ + key: 'name', + type: 'text' + }]; + + scope.options = {formDefaults: {}}; + + var tmpl = angular.element('
'); + + $compile(tmpl)(scope); + $rootScope.$apply(); + + expect(tmpl.find('input').attr('disabled')).to.be.undefined; + + var disable, enable; + disable = function () { + // form element should be disabled + scope.options.formDefaults.readonly = true; + scope.$broadcast('schemaFormRedraw'); + $rootScope.$apply(); + expect(tmpl.find('input').attr('disabled')).eq('disabled'); + + // try to re-enable it by modifying global option + setTimeout(enable, 0); + }; + + enable = function () { + // form element should be back to enabled + scope.options.formDefaults.readonly = false; + scope.$broadcast('schemaFormRedraw'); + $rootScope.$apply(); + expect(tmpl.find('input').attr('disabled')).to.be.undefined; + + done(); + } + + setTimeout(disable, 0); + }); + }); + it('should use supplied template with template field type',function() { inject(function($compile, $rootScope){ @@ -1847,8 +1846,7 @@ describe('directive',function(){ $compile(tmpl)(scope); $rootScope.$apply(); - - tmpl.children().eq(0).html().should.be.eq('
Hello World
') + tmpl.html().should.be.eq('
Hello World
') }); }); @@ -1886,7 +1884,7 @@ describe('directive',function(){ $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); - tmpl.children().eq(0).html().should.be.eq('
Hello World
') + tmpl.html().should.be.eq('
Hello World
'); }); }); @@ -1986,10 +1984,10 @@ describe('directive',function(){ ngModelCtrl.$valid = true; ngModelCtrl.$pristine = false; $rootScope.$apply(); - tmpl.children().eq(0).children().eq(0).hasClass('has-success').should.be.true; + tmpl.children().eq(0).hasClass('has-success').should.be.true; scope.form[0].disableSuccessState = true; $rootScope.$apply(); - tmpl.children().eq(0).children().eq(0).hasClass('has-success').should.be.false; + tmpl.children().eq(0).hasClass('has-success').should.be.false; }); }); @@ -2014,10 +2012,10 @@ describe('directive',function(){ ngModelCtrl.$invalid = true; ngModelCtrl.$pristine = false; $rootScope.$apply(); - tmpl.children().eq(0).children().eq(0).hasClass('has-error').should.be.true; + tmpl.children().eq(0).hasClass('has-error').should.be.true; scope.form[0].disableErrorState = true; $rootScope.$apply(); - tmpl.children().eq(0).children().eq(0).hasClass('has-error').should.be.false; + tmpl.children().eq(0).hasClass('has-error').should.be.false; }); }); }); @@ -2373,22 +2371,13 @@ describe('directive',function(){ } }); + + setTimeout(function() { scope.person.switch = false; scope.$apply(); - scope.person.should.deep.equal({ - "switch": false, - "list": [ - { - "sub": { - }, - } - ], - "deep": { - "sub": { - } - } + "switch": false }); done(); }); @@ -2523,7 +2512,6 @@ describe('directive',function(){ setTimeout(function() { scope.person.switch = false; scope.$apply(); - console.log(JSON.stringify(scope.person,undefined,2)) scope.person.should.deep.equal({ "switch": false, "list": [ diff --git a/test/directives/sf-messages-test.js b/test/directives/sf-messages-test.js index c41b07f62..8a872bc3c 100644 --- a/test/directives/sf-messages-test.js +++ b/test/directives/sf-messages-test.js @@ -1,7 +1,6 @@ chai.should(); describe('directive',function() { - beforeEach(module('templates')); beforeEach(module('schemaForm')); beforeEach( //We don't need no sanitation. We don't need no thought control. diff --git a/test/protractor/conf.js b/test/protractor/conf.js deleted file mode 100644 index 8f51a93ee..000000000 --- a/test/protractor/conf.js +++ /dev/null @@ -1,3 +0,0 @@ -exports.config = { - seleniumAddress: 'http://localhost:4444/wd/hub' -} diff --git a/test/protractor/specs/custom-validation.js b/test/protractor/specs/custom-validation.js deleted file mode 100644 index 98b05bf8b..000000000 --- a/test/protractor/specs/custom-validation.js +++ /dev/null @@ -1,74 +0,0 @@ -describe('Schema Form custom validators', function() { - it('should have a form with content', function() { - browser.get('http://localhost:8080/examples/custom-validators.html'); - - expect(element(by.css('form')).getInnerHtml()).not.toEqual(''); - }); - - describe('#name', function() { - it('should not complain if it gets a normal name', function() { - browser.get('http://localhost:8080/examples/custom-validators.html'); - var input = element.all(by.css('form input')).first(); - input.sendKeys('Joe Schmoe'); - - expect(input.getAttribute('value')).toEqual('Joe Schmoe'); - expect(input.evaluate('ngModel.$valid')).toEqual(true); - - }); - - it('should complain if it gets a "Bob" as a name', function() { - browser.get('http://localhost:8080/examples/custom-validators.html'); - var input = element.all(by.css('form input')).first(); - input.sendKeys('Bob'); - - expect(input.getAttribute('value')).toEqual('Bob'); - expect(input.evaluate('ngModel.$valid')).toEqual(false); - }); - }); - - describe('#email', function() { - it('should not complain if it gets a normal email', function() { - browser.get('http://localhost:8080/examples/custom-validators.html'); - var input = element.all(by.css('form input')).get(1); - input.sendKeys('foo@mailinator.com'); - - expect(input.getAttribute('value')).toEqual('foo@mailinator.com'); - expect(input.evaluate('ngModel.$valid')).toEqual(true); - - }); - - it('should complain if it gets a my email', function() { - browser.get('http://localhost:8080/examples/custom-validators.html'); - var input = element.all(by.css('form input')).get(1); - input.sendKeys('david.lgj@gmail.com'); - - expect(input.getAttribute('value')).toEqual('david.lgj@gmail.com'); - expect(input.evaluate('ngModel.$valid')).toEqual(false); - }); - }); - - describe('#comment', function() { - it('should not complain if it gets a normal email', function() { - browser.get('http://localhost:8080/examples/custom-validators.html'); - var input = element.all(by.css('form input')).get(1); - input.sendKeys('foo@mailinator.com'); - - expect(input.getAttribute('value')).toEqual('foo@mailinator.com'); - expect(input.evaluate('ngModel.$valid')).toEqual(true); - - }); - - it('should complain if it gets a my email', function() { - browser.get('http://localhost:8080/examples/custom-validators.html'); - var input = element.all(by.css('form input')).get(1); - input.sendKeys('david.lgj@gmail.com'); - - expect(input.getAttribute('value')).toEqual('david.lgj@gmail.com'); - expect(input.evaluate('ngModel.$valid')).toEqual(false); - }); - }); - - - - -}); diff --git a/test/protractor/specs/validation-messages.js b/test/protractor/specs/validation-messages.js deleted file mode 100644 index 323275649..000000000 --- a/test/protractor/specs/validation-messages.js +++ /dev/null @@ -1,66 +0,0 @@ -/* global browser, it, describe, element, by */ - -describe('Schema Form validation messages', function() { - - describe('#string', function() { - var URL = 'http://localhost:8080/examples/bootstrap-example.html#/86fb7505a8ab6a43bc70'; - - it('should not complain if it gets a normal string', function() { - browser.get(URL); - var input = element.all(by.css('form[name=ngform] input')).first(); - input.sendKeys('string'); - - expect(input.getAttribute('value')).toEqual('string'); - expect(input.evaluate('ngModel.$valid')).toEqual(true); - - }); - - - var validationMessageTestBuider = function(nr, value, validationMessage) { - it('should say "' + validationMessage + '" when input is ' + value, function() { - browser.get(URL); - var input = element.all(by.css('form[name=ngform] input')).get(nr); - input.sendKeys(value); - - var message = element.all(by.css('form[name=ngform] div[sf-message]')).get(nr); - expect(input.evaluate('ngModel.$valid')).toEqual(false); - expect(message.getText()).toEqual(validationMessage); - - }); - }; - - var stringTests = { - 's': 'String is too short (1 chars), minimum 3', - 'tooo long string': 'String is too long (11 chars), maximum 10', - 'foo 66': 'String does not match pattern: ^[a-zA-Z ]+$' - }; - - Object.keys(stringTests).forEach(function(value) { - validationMessageTestBuider(0, value, stringTests[value]); - }); - - - var integerTests = { - '3': '3 is less than the allowed minimum of 6', - '66': '66 is greater than the allowed maximum of 50', - '11': 'Value is not a multiple of 3', - 'aaa': 'Value is not a valid number' - }; - - Object.keys(integerTests).forEach(function(value) { - validationMessageTestBuider(1, value, integerTests[value]); - }); - - - it('should say "Required" when fields are required', function() { - browser.get(URL); - element.all(by.css('form[name=ngform]')).submit(); - var input = element.all(by.css('form[name=ngform] input')).get(1); - - var message = element.all(by.css('form[name=ngform] div[sf-message]')).get(1); - expect(input.evaluate('ngModel.$valid')).toEqual(false); - expect(message.getText()).toEqual('Required'); - - }); - }); -});