From c347200787d76e3641c742db886233c9ae5268a1 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Fri, 28 Mar 2014 14:43:41 +0100 Subject: [PATCH 01/10] Updated example --- problem.txt | 54 -------------------------------------- src/bootstrap-example.html | 21 ++++++++++----- 2 files changed, 14 insertions(+), 61 deletions(-) delete mode 100644 problem.txt diff --git a/problem.txt b/problem.txt deleted file mode 100644 index a4b5ca952..000000000 --- a/problem.txt +++ /dev/null @@ -1,54 +0,0 @@ - - - -Olika vägar frammåt: -==================== - - - -Problemställningen: -------------------- -json-form fixar en hel del validering, dels genom att läggapå attribut som -min och max på input och dels genom att validera via en schema validtor. - -Jag tänkte undvika validatorn och köra "the angular way" genom att ha all validering -i formuläret via HTML5 valideringar och directives som ng-pattern, ng-maxlength osv -antagligen ett par egna. - -Hittils har algoritmen sett ut såhär: -#1 schema => defaultForm, -#2 defaultForm+form => finalForm -#3 loopa finalForm och skapa html, dvs all info för validering i formuläret. - -Kruxet är att formulär definitionen i json form *inte* innehåller tillräckligt med valideringar -(den innehåller några) utan jag måste lägga till form attribut för dem. (json form tar dem från -schemat) - -tex så om schema innehåller "maxLength" attribut så sätts "maxlength" på input fältet, men om form -definitionen innehåller "maxLength" (eller "maxlength" för den delen) så händer inget. - -I min algoritm ovan där form definitionen måste innehålla all info för generering av html och -de attribut som styr upp validering så måste jag helt enkelt hitta på eget. - -Frågan är hur ska detta egna se ut? - - -#1 Egna form attribut -När det inte finns ngt form attribut i json forms defintion så skapar jag ett eget, t.ex. så skulle -maxLength bara kopieras till form objektet. -+ minsta motståndets lag -- kluddig lösning, motverkar lite att vara json form kompatibel -- kan allt stödjas? - - -#2 Eget schema directive -Istället skapa ett eget directive som antingen validerar själv eller lägger till de attribut som -behövs för att validera. Schemat fås genom require och ng-model värdet pekar ut vart i schemat. -Kan möjligtvis implementeras med tv4. -+ snygg lösning -- krångligt directive, måste ta hänsyn till om tex maximum, required osv redan satts via form -definitionen. - - - - diff --git a/src/bootstrap-example.html b/src/bootstrap-example.html index e865bb02f..6b80e9d84 100644 --- a/src/bootstrap-example.html +++ b/src/bootstrap-example.html @@ -5,12 +5,19 @@ @@ -22,16 +29,16 @@

Schema Form Example

The Generated Form

+

Model

+
{{pretty()}}
-

Schema

-

Form

-

Model

-
{{pretty()}}
+ ng-class="{red: !itParsesForm}" ng-model="formJson" class="form-control form">
+

Schema

+
From 32f98dcbc2bc61699131b7ad1a7ec515951f51a9 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Mon, 31 Mar 2014 09:43:48 +0200 Subject: [PATCH 02/10] Support forbuttons --- dist/bootstrap-decorator.min.js | 2 +- dist/schema-form.min.js | 2 +- .../decorators/bootstrap/actions.html | 5 +++- .../bootstrap/bootstrap-decorator.js | 1 + .../decorators/bootstrap/submit.html | 9 +++++- src/services/decorators.js | 8 +++++ test/schema-form-test.js | 30 ++++++++++++++++++- 7 files changed, 52 insertions(+), 5 deletions(-) diff --git a/dist/bootstrap-decorator.min.js b/dist/bootstrap-decorator.min.js index 45acec573..84d84fdd0 100644 --- a/dist/bootstrap-decorator.min.js +++ b/dist/bootstrap-decorator.min.js @@ -1 +1 @@ -!function(e){try{e=angular.module("schemaForm")}catch(r){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/actions.html",'
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(r){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/checkbox.html",'
{{form.description}}
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(r){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/checkboxes.html",'
{{form.description}}
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(r){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/default.html",'
{{form.description}} {{schemaError}}
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(r){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/fieldset.html",'
{{ form.title }}
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(r){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/readonly.html",'
{{form.description}}
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(r){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/section.html",'
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(r){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/select.html",'
{{form.description}} {{schemaError}}
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(r){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/submit.html",'
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(r){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/textarea.html",'
{{form.description}} {{schemaError}} {{ngModel.$error}}
')}])}(),angular.module("schemaForm").config(["schemaFormDecoratorsProvider",function(e){e.create("bootstrapDecorator",{textarea:"directives/decorators/bootstrap/textarea.html",fieldset:"directives/decorators/bootstrap/fieldset.html",section:"directives/decorators/bootstrap/section.html",actions:"directives/decorators/bootstrap/actions.html",select:"directives/decorators/bootstrap/select.html",checkbox:"directives/decorators/bootstrap/checkbox.html",checkboxes:"directives/decorators/bootstrap/checkboxes.html",number:"directives/decorators/bootstrap/default.html",submit:"directives/decorators/bootstrap/submit.html","default":"directives/decorators/bootstrap/default.html"},[function(e){return e.readonly&&e.key&&"fieldset"!==e.type?"directives/decorators/bootstrap/readonly.html":void 0}])}]); \ No newline at end of file +!function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/actions.html",'
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/checkbox.html",'
{{form.description}}
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/checkboxes.html",'
{{form.description}}
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/default.html",'
{{form.description}} {{schemaError}}
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/fieldset.html",'
{{ form.title }}
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/readonly.html",'
{{form.description}}
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/section.html",'
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/select.html",'
{{form.description}} {{schemaError}}
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/submit.html",'
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/textarea.html",'
{{form.description}} {{schemaError}} {{ngModel.$error}}
')}])}(),angular.module("schemaForm").config(["schemaFormDecoratorsProvider",function(e){e.create("bootstrapDecorator",{textarea:"directives/decorators/bootstrap/textarea.html",fieldset:"directives/decorators/bootstrap/fieldset.html",section:"directives/decorators/bootstrap/section.html",actions:"directives/decorators/bootstrap/actions.html",select:"directives/decorators/bootstrap/select.html",checkbox:"directives/decorators/bootstrap/checkbox.html",checkboxes:"directives/decorators/bootstrap/checkboxes.html",number:"directives/decorators/bootstrap/default.html",submit:"directives/decorators/bootstrap/submit.html",button:"directives/decorators/bootstrap/submit.html","default":"directives/decorators/bootstrap/default.html"},[function(e){return e.readonly&&e.key&&"fieldset"!==e.type?"directives/decorators/bootstrap/readonly.html":void 0}])}]); \ No newline at end of file diff --git a/dist/schema-form.min.js b/dist/schema-form.min.js index 835d0daa8..369993e23 100644 --- a/dist/schema-form.min.js +++ b/dist/schema-form.min.js @@ -1 +1 @@ -angular.module("schemaForm",[]),angular.module("schemaForm").provider("schemaFormDecorators",["$compileProvider",function(e){var r="",t={},n=function(e,n){"sfDecorator"===e&&(e=r);for(var a=t[e],i=a.rules,o=0;o - + diff --git a/src/directives/decorators/bootstrap/bootstrap-decorator.js b/src/directives/decorators/bootstrap/bootstrap-decorator.js index a3455b0d9..507df230b 100644 --- a/src/directives/decorators/bootstrap/bootstrap-decorator.js +++ b/src/directives/decorators/bootstrap/bootstrap-decorator.js @@ -10,6 +10,7 @@ angular.module('schemaForm').config(['schemaFormDecoratorsProvider',function(dec checkboxes: 'directives/decorators/bootstrap/checkboxes.html', number: 'directives/decorators/bootstrap/default.html', submit: 'directives/decorators/bootstrap/submit.html', + button: 'directives/decorators/bootstrap/submit.html', 'default': 'directives/decorators/bootstrap/default.html' },[ function(form){ diff --git a/src/directives/decorators/bootstrap/submit.html b/src/directives/decorators/bootstrap/submit.html index 697439576..fe11eeb06 100644 --- a/src/directives/decorators/bootstrap/submit.html +++ b/src/directives/decorators/bootstrap/submit.html @@ -1,3 +1,10 @@
- + +
\ No newline at end of file diff --git a/src/services/decorators.js b/src/services/decorators.js index 540eb00a2..32b51cfae 100644 --- a/src/services/decorators.js +++ b/src/services/decorators.js @@ -74,6 +74,14 @@ angular.module('schemaForm').provider('schemaFormDecorators',['$compileProvider' }); return lst; }; + + scope.clickButton = function($event,form) { + if (angular.isFunction(form.onClick)) { + form.onClick($event,form); + } else if (angular.isString(form.onClick)) { + scope.$eval(form.onClick,{'$event':$event,form:form}); + } + }; } }; }]); diff --git a/test/schema-form-test.js b/test/schema-form-test.js index bddf3a846..391cedf46 100644 --- a/test/schema-form-test.js +++ b/test/schema-form-test.js @@ -200,7 +200,35 @@ describe('Schema form',function(){ 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'); + }); + }); + + it('should handle buttons',function(){ + + inject(function($compile,$rootScope){ + var scope = $rootScope.$new(); + scope.obj = {}; + + scope.schema = exampleSchema; + + scope.form = ["*",{ type: 'button',title: 'Okidoki', onClick: sinon.spy()}]; + + var tmpl = angular.element('
'); + + $compile(tmpl)(scope); + $rootScope.$apply(); + + tmpl.children().length.should.be.equal(3); + 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.be.equal('Okidoki'); + scope.form[1].onClick.should.not.have.beenCalled; + tmpl.children().eq(2).find('button').click(); + scope.form[1].onClick.should.have.beenCalledOnce; }); }); @@ -775,7 +803,7 @@ describe('Schema form',function(){ }); describe('decorator factory service',function(){ - it.only('should enable you to create new decorator directives',function(){ + it('should enable you to create new decorator directives',function(){ module(function(schemaFormDecoratorsProvider){ schemaFormDecoratorsProvider.create('foobar',{ 'foo':'/bar.html' },[angular.noop]); }); From 3d514d2eb157c78f290dea226d04687b79e17bdc Mon Sep 17 00:00:00 2001 From: David Jensen Date: Tue, 1 Apr 2014 10:13:03 +0200 Subject: [PATCH 03/10] Manual field definitions Reuse decorator functions in handcrafted forms. --- .../decorators/bootstrap/actions-trcl.html | 1 + .../bootstrap/bootstrap-decorator.js | 53 ++++++++--- .../decorators/bootstrap/default.html | 3 +- .../decorators/bootstrap/fieldset-trcl.html | 4 + src/directives/schema-validate.js | 5 + src/field-example.html | 81 ++++++++++++++++ src/services/decorators.js | 95 +++++++++++++++++-- test/schema-form-test.js | 2 +- 8 files changed, 219 insertions(+), 25 deletions(-) create mode 100644 src/directives/decorators/bootstrap/actions-trcl.html create mode 100644 src/directives/decorators/bootstrap/fieldset-trcl.html create mode 100644 src/field-example.html diff --git a/src/directives/decorators/bootstrap/actions-trcl.html b/src/directives/decorators/bootstrap/actions-trcl.html new file mode 100644 index 000000000..092439c40 --- /dev/null +++ b/src/directives/decorators/bootstrap/actions-trcl.html @@ -0,0 +1 @@ +
diff --git a/src/directives/decorators/bootstrap/bootstrap-decorator.js b/src/directives/decorators/bootstrap/bootstrap-decorator.js index 507df230b..90146e5cd 100644 --- a/src/directives/decorators/bootstrap/bootstrap-decorator.js +++ b/src/directives/decorators/bootstrap/bootstrap-decorator.js @@ -1,26 +1,51 @@ angular.module('schemaForm').config(['schemaFormDecoratorsProvider',function(decoratorsProvider){ + var base = 'directives/decorators/bootstrap/'; - decoratorsProvider.create('bootstrapDecorator',{ - textarea: 'directives/decorators/bootstrap/textarea.html', - fieldset: 'directives/decorators/bootstrap/fieldset.html', - section: 'directives/decorators/bootstrap/section.html', - actions: 'directives/decorators/bootstrap/actions.html', - select: 'directives/decorators/bootstrap/select.html', - checkbox: 'directives/decorators/bootstrap/checkbox.html', - checkboxes: 'directives/decorators/bootstrap/checkboxes.html', - number: 'directives/decorators/bootstrap/default.html', - submit: 'directives/decorators/bootstrap/submit.html', - button: 'directives/decorators/bootstrap/submit.html', - 'default': 'directives/decorators/bootstrap/default.html' + decoratorsProvider.createDecorator('bootstrapDecorator',{ + textarea: base+'textarea.html', + fieldset: base+'fieldset.html', + section: base+'section.html', + actions: base+'actions.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', + 'default': base+'default.html' },[ function(form){ if (form.readonly && form.key && form.type !== 'fieldset') { - return 'directives/decorators/bootstrap/readonly.html'; + return base+'readonly.html'; } } ]); -}]); + //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', + input: base+'default.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/default.html b/src/directives/decorators/bootstrap/default.html index 9600587a1..ef43009fc 100644 --- a/src/directives/decorators/bootstrap/default.html +++ b/src/directives/decorators/bootstrap/default.html @@ -1,7 +1,8 @@
- + {{ form.title }} +
+ \ No newline at end of file diff --git a/src/directives/schema-validate.js b/src/directives/schema-validate.js index 595b4a390..64f143b67 100644 --- a/src/directives/schema-validate.js +++ b/src/directives/schema-validate.js @@ -13,6 +13,11 @@ angular.module('schemaForm').directive('schemaValidate',function(){ schema = scope.$eval(attrs.schemaValidate); } + //Still might be undefined, especially if form has no schema... + if (!schema) { + return viewValue; + } + //required is handled by ng-required if (angular.isUndefined(viewValue)) { return undefined; diff --git a/src/field-example.html b/src/field-example.html new file mode 100644 index 000000000..a349ace14 --- /dev/null +++ b/src/field-example.html @@ -0,0 +1,81 @@ + + + + Boostrap Schema Form example + + + + + + +
+

Field Example

+ +
+
+ + + + + + + + + +
+

Model

+
{{pretty()}}
+
+ +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/src/services/decorators.js b/src/services/decorators.js index 32b51cfae..695fbef4a 100644 --- a/src/services/decorators.js +++ b/src/services/decorators.js @@ -42,7 +42,6 @@ angular.module('schemaForm').provider('schemaFormDecorators',['$compileProvider' //rebind our part of the form to the scope. var once = scope.$watch(attrs.form,function(form){ - if (form) { scope.form = form; @@ -51,7 +50,7 @@ angular.module('schemaForm').provider('schemaFormDecorators',['$compileProvider' //for fieldsets to recurse properly. var url = templateUrl(name,form); $http.get(url,{ cache: $templateCache }).then(function(res){ - var template = res.data.replace(/\$\$value\$\$/g,'model.'+form.key); + var template = res.data.replace(/\$\$value\$\$/g,'model.'+(form.key || "")); $compile(template)(scope,function(clone){ element.replaceWith(clone); }); @@ -87,24 +86,78 @@ angular.module('schemaForm').provider('schemaFormDecorators',['$compileProvider' }]); }; + var createManualDirective = function(type,templateUrl,transclude) { + transclude = angular.isDefined(transclude)? transclude : false; + $compileProvider.directive('sf'+angular.uppercase(type[0])+type.substr(1), function(){ + return { + restrict: "EAC", + scope: true, + replace: true, + transclude: transclude, + template: '', + link: function(scope,element,attrs) { + var watchThis = { + 'items': 'c', + 'titleMap': 'c', + 'schema': 'c' + }; + var form = { type: type }; + var once = true; + angular.forEach(attrs,function(value,name){ + if (name[0] !== '$' && name.indexOf('ng') !== 0 && name !== 'sfField') { + + var updateForm = function(val){ + if (angular.isDefined(val) && val !== form[name]) { + form[name] = val; + + //when we have type, and if specified key we apply it on scope. + if (once && form.type && (form.key || angular.isUndefined(attrs.key))) { + scope.form = form; + once = false; + } + } + }; + + if (name === 'model') { + //"model" is bound to scope under the name "model" since this is what the decorators + //know and love. + scope.$watch(value,function(val){ + if (val && scope.model !== val) { + scope.model = val; + } + }); + } else if (watchThis[name] === 'c') { + //watch collection + scope.$watchCollection(value,updateForm); + } else { + //$observe + attrs.$observe(name,updateForm); + } + } + }); + } + }; + }); + }; + + + /** - * Create a decorator directive + * Create a decorator directive and its sibling "manual" use directives. * The directive can be used to create form fields or other form entities. * It can be used in conjunction with directive in which case the decorator is * given it's configuration via a the "form" attribute. * - * ex. Basic usage with form and schema - * - * - * ex. "Manual" usage - * + ** * @param {string} name directive name (CamelCased) * @param {Object} mappings, an object that maps "type" => "templateUrl" * @param {Array} rules (optional) a list of functions, function(form){}, that are each tried in turn, * if they return a string then that is used as the templateUrl. Rules come before * mappings. */ - this.create = function(name,mappings,rules){ + this.createDecorator = function(name,mappings,rules){ directives[name] = { mappings: mappings || {}, rules: rules || [] @@ -116,6 +169,30 @@ angular.module('schemaForm').provider('schemaFormDecorators',['$compileProvider' createDirective(name); }; + /** + * Creates a directive of a decorator + * Usable when you want to use the decorators without using directive. + * Specifically when you need to reuse styling. + * + * ex. createDirective('text','...') + * + * + * @param {string} type The type of the directive, resulting directive will have sf- prefixed + * @param {string} templateUrl + * @param {boolean} transclude (optional) sets transclude option of directive, defaults to false. + */ + this.createDirective = createManualDirective; + + /** + * Same as createDirective, but takes an object where key is 'type' and value is 'templateUrl' + * Useful for batching. + * @param {Object} mappings + */ + this.createDirectives = function(mappings) { + angular.forEach(mappings,function(url,type){ + createManualDirective(type,url); + }); + }; /** * Getter for directive mappings diff --git a/test/schema-form-test.js b/test/schema-form-test.js index 391cedf46..4961f446d 100644 --- a/test/schema-form-test.js +++ b/test/schema-form-test.js @@ -805,7 +805,7 @@ describe('Schema form',function(){ describe('decorator factory service',function(){ it('should enable you to create new decorator directives',function(){ module(function(schemaFormDecoratorsProvider){ - schemaFormDecoratorsProvider.create('foobar',{ 'foo':'/bar.html' },[angular.noop]); + schemaFormDecoratorsProvider.createDecorator('foobar',{ 'foo':'/bar.html' },[angular.noop]); }); inject(function($rootScope,$compile,$templateCache){ From c5bcfcbb4a64e9ee5920a3f14c559feae501c297 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Tue, 1 Apr 2014 10:27:04 +0200 Subject: [PATCH 04/10] Changed html2js plugin From gulp-ng-html2js to gulp-angular-templatecache since the latter didn't prefix every module with a try cache. Approx 33% reduction in size in our case. --- gulpfile.js | 9 +++++---- package.json | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 92b6e226b..e031ab09b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -2,7 +2,7 @@ var gulp = require('gulp'); -var ngHtml2Js = require("gulp-ng-html2js"); +var templateCache = require('gulp-angular-templatecache'); var minifyHtml = require("gulp-minify-html"); var concat = require("gulp-concat"); var uglify = require("gulp-uglify"); @@ -18,9 +18,9 @@ gulp.task('bootstrap', function() { spare: true, quotes: true })) - .pipe(ngHtml2Js({ - moduleName: "schemaForm", - prefix: "directives/decorators/bootstrap/" + .pipe(templateCache({ + module: "schemaForm", + root: "directives/decorators/bootstrap/" })) ); stream.queue(gulp.src('./src/directives/decorators/bootstrap/*.js')); @@ -32,6 +32,7 @@ gulp.task('bootstrap', function() { }); + gulp.task('minify',function(){ gulp.src([ './src/module.js', diff --git a/package.json b/package.json index f17cf8916..9286cf3a2 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,10 @@ "karma-growler-reporter": "0.0.1", "karma-ng-html2js-preprocessor": "^0.1.0", "gulp": "^3.5.6", - "gulp-ng-html2js": "^0.1.6", "gulp-uglify": "^0.2.1", "gulp-minify-html": "^0.1.1", "gulp-concat": "^2.2.0", - "streamqueue": "0.0.5" + "streamqueue": "0.0.5", + "gulp-angular-templatecache": "^1.1.1" } } From f12e9d3c03dbdb84a6d0ba7c831c36b253e82bde Mon Sep 17 00:00:00 2001 From: David Jensen Date: Thu, 3 Apr 2014 11:57:07 +0200 Subject: [PATCH 05/10] Button onClick is now evaled in proper scope It was evaled in the isolated scope of the sf-schema direcive, now we lift it up to the parent outside. --- src/bootstrap-example.html | 5 ++++- src/directives/decorators/bootstrap/actions.html | 4 ++-- src/directives/decorators/bootstrap/submit.html | 2 +- src/directives/schema-form.js | 5 +++++ src/services/decorators.js | 12 +++++++++--- src/services/schema-form.js | 4 ++-- 6 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/bootstrap-example.html b/src/bootstrap-example.html index 6b80e9d84..11df7a66f 100644 --- a/src/bootstrap-example.html +++ b/src/bootstrap-example.html @@ -127,7 +127,7 @@

Schema

type: 'actions', items: [ { type: 'submit', title: 'Do It!'}, - { type: 'button', title: 'Noooooooooooo'} + { type: 'button', title: 'Noooooooooooo', onClick: 'sayNo()'} ] } ]; @@ -167,6 +167,9 @@

Schema

}; + $scope.sayNo = function() { + alert('Noooooooo') + } } diff --git a/src/directives/decorators/bootstrap/actions.html b/src/directives/decorators/bootstrap/actions.html index 74d96665c..2ed3bce84 100644 --- a/src/directives/decorators/bootstrap/actions.html +++ b/src/directives/decorators/bootstrap/actions.html @@ -3,9 +3,9 @@ type="submit" class="btn btn-primary" value="{{item.title}}" - ng-click="buttonClick($event,form)" ng-if="item.type === 'submit'"> + ng-click="buttonClick($event,item)">{{item.title}} diff --git a/src/directives/decorators/bootstrap/submit.html b/src/directives/decorators/bootstrap/submit.html index fe11eeb06..3c7dab2bc 100644 --- a/src/directives/decorators/bootstrap/submit.html +++ b/src/directives/decorators/bootstrap/submit.html @@ -2,9 +2,9 @@ \ No newline at end of file diff --git a/src/directives/schema-form.js b/src/directives/schema-form.js index 0f964850b..4f4496b52 100644 --- a/src/directives/schema-form.js +++ b/src/directives/schema-form.js @@ -32,6 +32,11 @@ function($compile, schemaForm, schemaFormDecorators){ initialForm: '=sfForm', model: '=sfModel' }, + controller: ['$scope',function($scope){ + this.evalInParentScope = function(expr,locals){ + $scope.$parent.$eval(expr,locals); + }; + }], replace: false, restrict: "A", transclude: true, diff --git a/src/services/decorators.js b/src/services/decorators.js index 695fbef4a..ffe85c727 100644 --- a/src/services/decorators.js +++ b/src/services/decorators.js @@ -38,7 +38,8 @@ angular.module('schemaForm').provider('schemaFormDecorators',['$compileProvider' replace: true, transclude: false, scope: true, - link: function(scope,element,attrs) { + require: '?^sfSchema', + link: function(scope,element,attrs,sfSchema) { //rebind our part of the form to the scope. var once = scope.$watch(attrs.form,function(form){ @@ -74,11 +75,16 @@ angular.module('schemaForm').provider('schemaFormDecorators',['$compileProvider' return lst; }; - scope.clickButton = function($event,form) { + scope.buttonClick = function($event,form) { if (angular.isFunction(form.onClick)) { form.onClick($event,form); } else if (angular.isString(form.onClick)) { - scope.$eval(form.onClick,{'$event':$event,form:form}); + if (sfSchema) { + //evaluating in scope outside of sfSchemas isolated scope + sfSchema.evalInParentScope(form.onClick,{'$event':$event,form:form}); + } else { + scope.$eval(form.onClick,{'$event':$event,form:form}); + } } }; } diff --git a/src/services/schema-form.js b/src/services/schema-form.js index 4e8e29bb4..c14f3cd06 100644 --- a/src/services/schema-form.js +++ b/src/services/schema-form.js @@ -5,7 +5,7 @@ */ angular.module('schemaForm').factory('schemaForm',[function(){ var service = {}; - + service.merge = function(schema,form,ignore) { form = form || ["*"]; @@ -24,7 +24,7 @@ angular.module('schemaForm').factory('schemaForm',[function(){ //We look at the supplied form and extend it with schema standards var lookup = stdForm.lookup; return form.map(function(obj){ - + //handle the shortcut with just a name if (typeof obj === 'string') { obj = { key: obj }; From 459a1a53ac0d2193c0f56a28af81b84459f9b4d8 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Tue, 22 Apr 2014 10:35:36 +0200 Subject: [PATCH 06/10] Schema now merges properly with form in fieldsets --- karma.conf.js | 6 +- .../decorators/bootstrap/fieldset.html | 2 +- src/services/schema-form.js | 11 +++- test/schema-form-test.js | 59 +++++++++++++++++++ 4 files changed, 72 insertions(+), 6 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index eb92edf4d..b2573771e 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -15,8 +15,8 @@ module.exports = function(config) { // list of files / patterns to load in the browser files: [ 'http://code.jquery.com/jquery-2.1.0.min.js', - 'https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js', - 'http://code.angularjs.org/1.2.14/angular-mocks.js', + 'https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js', + 'https://code.angularjs.org/1.2.16/angular-mocks.js', 'bower_components/tv4/tv4.js', 'src/module.js', 'src/services/*.js', @@ -81,7 +81,7 @@ module.exports = function(config) { // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`) // - PhantomJS // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`) - browsers: ['PhantomJS'], + browsers: [], // If browser does not capture in given timeout [ms], kill it diff --git a/src/directives/decorators/bootstrap/fieldset.html b/src/directives/decorators/bootstrap/fieldset.html index a3cc6bf86..1f4ebd19f 100644 --- a/src/directives/decorators/bootstrap/fieldset.html +++ b/src/directives/decorators/bootstrap/fieldset.html @@ -1,4 +1,4 @@
{{ form.title }} - +
\ No newline at end of file diff --git a/src/services/schema-form.js b/src/services/schema-form.js index c14f3cd06..40bfb1709 100644 --- a/src/services/schema-form.js +++ b/src/services/schema-form.js @@ -5,7 +5,7 @@ */ angular.module('schemaForm').factory('schemaForm',[function(){ var service = {}; - + service.merge = function(schema,form,ignore) { form = form || ["*"]; @@ -24,12 +24,18 @@ angular.module('schemaForm').factory('schemaForm',[function(){ //We look at the supplied form and extend it with schema standards var lookup = stdForm.lookup; return form.map(function(obj){ - + //handle the shortcut with just a name if (typeof obj === 'string') { obj = { key: obj }; } + //if it's a type with items, merge 'em! + if (obj.items) { + obj.items = service.merge(schema,obj.items,ignore); + } + + //extend with std form from schema. if (obj.key && lookup[obj.key]) { return angular.extend(lookup[obj.key],obj); @@ -133,6 +139,7 @@ angular.module('schemaForm').factory('schemaForm',[function(){ var f = stdFormObj(schema,options); f.type = 'fieldset'; f.items = []; + options.lookup[options.path] = f; //recurse down into properties angular.forEach(schema.properties,function(v,k){ diff --git a/test/schema-form-test.js b/test/schema-form-test.js index 4961f446d..c226261bb 100644 --- a/test/schema-form-test.js +++ b/test/schema-form-test.js @@ -468,6 +468,65 @@ describe('Schema form',function(){ }); }); + it('should handle schema form defaults in deep structure',function(){ + + inject(function($compile,$rootScope){ + var scope = $rootScope.$new(); + scope.person = { + name: 'Foobar' + }; + + scope.schema = { + "type": "object", + "properties": { + "props" : { + "type": "object", + "title": "Person", + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "nick": { + "type": "string", + "title": "Nick" + }, + "alias": { + "type": "string", + "title": "Alias" + } + } + } + } + }; + + //The form defines a fieldset for person, and changes the order of fields + //but titles should come from the schema + scope.form = [{ + type: 'fieldset', + key: 'props', + items: [ + 'props.nick', + 'props.name', + 'props.alias' + ] + }]; + + var tmpl = angular.element('
'); + + $compile(tmpl)(scope); + $rootScope.$apply(); + + tmpl.children().length.should.be.eq(1); + var labels = tmpl.children().children().find('label'); + labels.eq(0).text().should.equal('Nick'); + labels.eq(1).text().should.equal('Name'); + labels.eq(2).text().should.equal('Alias'); + + }); + }); + + it('should skip title if form says "notitle"',function(){ inject(function($compile,$rootScope){ From ad48da7fa2c6bb8159aef15029cc4eb477911de5 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Tue, 22 Apr 2014 15:54:21 +0200 Subject: [PATCH 07/10] Basic support for custom validation messages By default the validation messages comes from tv4.js, but now you can specify your own via the "validationMessages" attribute either in schema (not recomended) or in the form defintion. If it's a string, that is presented to the user on any error *but* required. If it's an object that is treated as an map between tv4.js error codes and a messages. Special "keys" are 'required' and 'default' that do what you think they do. --- .../decorators/bootstrap/default.html | 3 +- .../decorators/bootstrap/select.html | 3 +- .../decorators/bootstrap/textarea.html | 3 +- src/directives/schema-validate.js | 8 ++- src/services/decorators.js | 29 +++++++++++ src/services/schema-form.js | 24 ++++++--- test/schema-form-test.js | 50 +++++++++++++++++++ 7 files changed, 105 insertions(+), 15 deletions(-) diff --git a/src/directives/decorators/bootstrap/default.html b/src/directives/decorators/bootstrap/default.html index ef43009fc..704c6001d 100644 --- a/src/directives/decorators/bootstrap/default.html +++ b/src/directives/decorators/bootstrap/default.html @@ -9,6 +9,5 @@ ng-model="$$value$$" schema-validate="form.schema"> - {{form.description}} - {{schemaError}} + {{ (hasError() && errorMessage(schemaError())) || form.description}} \ No newline at end of file diff --git a/src/directives/decorators/bootstrap/select.html b/src/directives/decorators/bootstrap/select.html index 11681c309..67f5b7565 100644 --- a/src/directives/decorators/bootstrap/select.html +++ b/src/directives/decorators/bootstrap/select.html @@ -8,6 +8,5 @@ ng-required="form.required" ng-options="val as name for (val,name) in form.titleMap"> - {{form.description}} - {{schemaError}} + {{ (hasError() && errorMessage(schemaError())) || form.description}} \ No newline at end of file diff --git a/src/directives/decorators/bootstrap/textarea.html b/src/directives/decorators/bootstrap/textarea.html index 1c8fc3729..035d8d55a 100644 --- a/src/directives/decorators/bootstrap/textarea.html +++ b/src/directives/decorators/bootstrap/textarea.html @@ -5,6 +5,5 @@ ng-model="$$value$$" schema-validate="form.schema"> - {{form.description}} - {{schemaError}} {{ngModel.$error}} + {{ (hasError() && errorMessage(schemaError())) || form.description}} \ No newline at end of file diff --git a/src/directives/schema-validate.js b/src/directives/schema-validate.js index 64f143b67..373670872 100644 --- a/src/directives/schema-validate.js +++ b/src/directives/schema-validate.js @@ -6,6 +6,7 @@ angular.module('schemaForm').directive('schemaValidate',function(){ require: 'ngModel', link: function(scope,element,attrs,ngModel) { scope.ngModel = ngModel; + var error = null; var schema = scope.$eval(attrs.schemaValidate); ngModel.$parsers.unshift(function(viewValue) { @@ -43,11 +44,12 @@ angular.module('schemaForm').directive('schemaValidate',function(){ if (result.valid) { // it is valid ngModel.$setValidity('schema', true); + error = null; return viewValue; } else { // it is invalid, return undefined (no model update) ngModel.$setValidity('schema', false); - scope.schemaError = result.error.message; + error = result.error; return undefined; } }); @@ -57,6 +59,10 @@ angular.module('schemaForm').directive('schemaValidate',function(){ return scope.ngModel.$invalid && !scope.ngModel.$pristine; }; + scope.schemaError = function() { + return error; + }; + } }; }); \ No newline at end of file diff --git a/src/services/decorators.js b/src/services/decorators.js index ffe85c727..37a12d5d9 100644 --- a/src/services/decorators.js +++ b/src/services/decorators.js @@ -87,6 +87,35 @@ angular.module('schemaForm').provider('schemaFormDecorators',['$compileProvider' } } }; + + /** + * Error message handler + * An error can either be a schema validation message or a angular js validtion + * error (i.e. required) + */ + scope.errorMessage = function(schemaError,$error) { + //User has supplied validation messages + if (scope.form.validationMessage) { + if (schemaError) { + if (angular.isString(scope.form.validationMessage)) { + return scope.form.validationMessage; + } + + return scope.form.validationMessage[schemaError.code] || scope.form.validationMessage.default; + } else { + return scope.form.validationMessage.required || scope.form.validationMessage; + } + } + + //No user supplied validation message. + if (schemaError) { + return schemaError.message; //use tv4.js validation message + } + + //Otherwise we only use required so it must be it. + return "Required"; + + }; } }; }]); diff --git a/src/services/schema-form.js b/src/services/schema-form.js index 40bfb1709..e6f767440 100644 --- a/src/services/schema-form.js +++ b/src/services/schema-form.js @@ -57,6 +57,10 @@ angular.module('schemaForm').factory('schemaForm',[function(){ if (schema.readOnly || schema.readonly) f.readonly = schema.readOnly || schema.readonly; if (schema.minimum) f.minimum = schema.minimum + (schema.exclusiveMinimum?1:0); if (schema.maximum) f.maximum = schema.maximum - (schema.exclusiveMaximum?1:0); + + //Non standard attributes + if (schema.validationMessage) f.validationMessage = schema.validationMessage; + if (schema.enumNames) f.titleMap = schema.enumNames; f.schema = schema; return f; }; @@ -110,10 +114,12 @@ angular.module('schemaForm').factory('schemaForm',[function(){ var f = stdFormObj(schema,options); f.key = options.path; f.type = 'select'; - f.titleMap = {}; - schema.enum.forEach(function(name){ - f.titleMap[name] = name; - }); + if (!f.titleMap) { + f.titleMap = {}; + schema.enum.forEach(function(name){ + f.titleMap[name] = name; + }); + } options.lookup[options.path] = f; return f; } @@ -124,10 +130,12 @@ angular.module('schemaForm').factory('schemaForm',[function(){ var f = stdFormObj(schema,options); f.key = options.path; f.type = 'checkboxes'; - f.titleMap = {}; - schema.items.enum.forEach(function(name){ - f.titleMap[name] = name; - }); + if (!f.titleMap) { + f.titleMap = {}; + schema.items.enum.forEach(function(name){ + f.titleMap[name] = name; + }); + } options.lookup[options.path] = f; return f; } diff --git a/test/schema-form-test.js b/test/schema-form-test.js index c226261bb..245ac5c76 100644 --- a/test/schema-form-test.js +++ b/test/schema-form-test.js @@ -328,6 +328,56 @@ describe('Schema form',function(){ }); }); + it('should display custom validationMessages when specified',function(done){ + + inject(function($compile,$rootScope){ + var scope = $rootScope.$new(); + scope.person = {}; + + scope.schema = { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z]+", + "validationMessage": "You are only allowed lower case letters in name." + }, + "nick": { + "type": "string", + "pattern": "^[a-z]+", + }, + } + }; + + scope.form = [ + "name", + { + key: 'nick', + validationMessage: 'Foobar' + } + ]; + + var tmpl = angular.element('
'); + + $compile(tmpl)(scope); + $rootScope.$apply(); + tmpl.find('input').each(function(){ + $(this).scope().ngModel.$setViewValue('AÖ'); + }); + + var errors = tmpl.find('.help-block'); + + //timeout so we can do a second $apply + setTimeout(function(){ + $rootScope.$apply(); //this actually updates the view with error messages + errors.eq(0).text().should.be.equal("You are only allowed lower case letters in name."); + errors.eq(1).text().should.be.equal("Foobar"); + done(); + },0); + + }); + }); + it('should use ng-required on required fields',function(){ From 2e593dc969782a70c7e6dbe5eb200553abd1d192 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Wed, 23 Apr 2014 10:35:32 +0200 Subject: [PATCH 08/10] Bugfix and docs for validationMessage --- README.md | 23 +++++++++++++++++++++++ src/services/decorators.js | 6 +++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c3c2c579a..b633c31e2 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,29 @@ General options most field types can handle: title: "Street", //Title of field, taken from schema if available notitle: false, //Set to true to hide title description: "Street name", //A description, taken from schema if available + validationMessage: "Oh noes, please write a proper address" //A custom validation error message +} +``` + +Validation Messages +------------------- +Per default all error messages but "Required" comes from the schema validator +[tv4](https://github.com/geraintluff/tv4), this might or might not work for you. +If you supply a ´´´validationMessage´´´ proṕerty in the form definition, and if its value is a +string that will be used instead on any validation error. + +If you need more fine grained control you can supply an object instead with keys matching the error +codes of [tv4](https://github.com/geraintluff/tv4). See ```tv4.errorCodes``` + +Ex. +```javascript +{ + key: "address.street", + validationMessage: { + tv4.errorCodes.STRING_LENGTH_SHORT: "Address is too short, man.", + "default": "Just write a proper address, will you?", //Special catch all error message + "required": "I needz an address plz" //Used for required if specified + } } ``` diff --git a/src/services/decorators.js b/src/services/decorators.js index 37a12d5d9..002246cce 100644 --- a/src/services/decorators.js +++ b/src/services/decorators.js @@ -93,7 +93,7 @@ angular.module('schemaForm').provider('schemaFormDecorators',['$compileProvider' * An error can either be a schema validation message or a angular js validtion * error (i.e. required) */ - scope.errorMessage = function(schemaError,$error) { + scope.errorMessage = function(schemaError) { //User has supplied validation messages if (scope.form.validationMessage) { if (schemaError) { @@ -101,9 +101,9 @@ angular.module('schemaForm').provider('schemaFormDecorators',['$compileProvider' return scope.form.validationMessage; } - return scope.form.validationMessage[schemaError.code] || scope.form.validationMessage.default; + return scope.form.validationMessage[schemaError.code] || scope.form.validationMessage['default']; } else { - return scope.form.validationMessage.required || scope.form.validationMessage; + return scope.form.validationMessage.required || scope.form.validationMessage['default'] || scope.form.validationMessage; } } From d9d3cba0163664014f4339aaad8c8a8e70743260 Mon Sep 17 00:00:00 2001 From: David Jensen Date: Wed, 23 Apr 2014 10:48:12 +0200 Subject: [PATCH 09/10] Added a CHANGELOG, updated README with buttons ...and a new build --- CHANGELOG | 6 ++++++ README.md | 12 +++++++++++- dist/bootstrap-decorator.min.js | 2 +- dist/schema-form.min.js | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 CHANGELOG diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 000000000..aaf0dab9c --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,6 @@ +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. diff --git a/README.md b/README.md index b633c31e2..c67e0997a 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Schema Form currently supports the following form field types: |:--------------|:------------------------| | fieldset | a fieldset with legend | | section | just a div | -| actions | horizontal button list, can only submit buttons as items | +| actions | horizontal button list, can only submit and buttons as items | | text | input with type text | | textarea | a textarea | | number | input type number | @@ -69,6 +69,7 @@ Schema Form currently supports the following form field types: | checkboxes | list of checkboxes | | select | a select (single value)| | submit | a submit button | +| button | a button | @@ -232,7 +233,16 @@ and the value is the title of the option. type: "actions", items: [ { type: 'submit', title: 'Ok' } + { type: 'button', title: 'Cancel', onClick: "cancel()" } ] } ``` +*button* can have a ```onClick``` attribute that either, as in JSON Form, is a function *or* a +string with an angular expression, as with ng-click. +[ + { type: 'button', title: 'Ok', onClick: function(){ ... } } + { type: 'button', title: 'Cancel', onClick: "cancel()" } +[ +``` + diff --git a/dist/bootstrap-decorator.min.js b/dist/bootstrap-decorator.min.js index 84d84fdd0..3ddc52176 100644 --- a/dist/bootstrap-decorator.min.js +++ b/dist/bootstrap-decorator.min.js @@ -1 +1 @@ -!function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/actions.html",'
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/checkbox.html",'
{{form.description}}
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/checkboxes.html",'
{{form.description}}
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/default.html",'
{{form.description}} {{schemaError}}
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/fieldset.html",'
{{ form.title }}
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/readonly.html",'
{{form.description}}
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/section.html",'
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/select.html",'
{{form.description}} {{schemaError}}
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/submit.html",'
')}])}(),function(e){try{e=angular.module("schemaForm")}catch(t){e=angular.module("schemaForm",[])}e.run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/textarea.html",'
{{form.description}} {{schemaError}} {{ngModel.$error}}
')}])}(),angular.module("schemaForm").config(["schemaFormDecoratorsProvider",function(e){e.create("bootstrapDecorator",{textarea:"directives/decorators/bootstrap/textarea.html",fieldset:"directives/decorators/bootstrap/fieldset.html",section:"directives/decorators/bootstrap/section.html",actions:"directives/decorators/bootstrap/actions.html",select:"directives/decorators/bootstrap/select.html",checkbox:"directives/decorators/bootstrap/checkbox.html",checkboxes:"directives/decorators/bootstrap/checkboxes.html",number:"directives/decorators/bootstrap/default.html",submit:"directives/decorators/bootstrap/submit.html",button:"directives/decorators/bootstrap/submit.html","default":"directives/decorators/bootstrap/default.html"},[function(e){return e.readonly&&e.key&&"fieldset"!==e.type?"directives/decorators/bootstrap/readonly.html":void 0}])}]); \ No newline at end of file +angular.module("schemaForm").run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/actions-trcl.html",'
'),e.put("directives/decorators/bootstrap/actions.html",'
'),e.put("directives/decorators/bootstrap/checkbox.html",'
{{form.description}}
'),e.put("directives/decorators/bootstrap/checkboxes.html",'
{{form.description}}
'),e.put("directives/decorators/bootstrap/default.html",'
{{ (hasError() && errorMessage(schemaError())) || form.description}}
'),e.put("directives/decorators/bootstrap/fieldset-trcl.html",'
{{ form.title }}
'),e.put("directives/decorators/bootstrap/fieldset.html",'
{{ form.title }}
'),e.put("directives/decorators/bootstrap/readonly.html",'
{{form.description}}
'),e.put("directives/decorators/bootstrap/section.html",'
'),e.put("directives/decorators/bootstrap/select.html",'
{{ (hasError() && errorMessage(schemaError())) || form.description}}
'),e.put("directives/decorators/bootstrap/submit.html",'
'),e.put("directives/decorators/bootstrap/textarea.html",'
{{ (hasError() && errorMessage(schemaError())) || form.description}}
')}]),angular.module("schemaForm").config(["schemaFormDecoratorsProvider",function(e){var t="directives/decorators/bootstrap/";e.createDecorator("bootstrapDecorator",{textarea:t+"textarea.html",fieldset:t+"fieldset.html",section:t+"section.html",actions:t+"actions.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","default":t+"default.html"},[function(e){return e.readonly&&e.key&&"fieldset"!==e.type?t+"readonly.html":void 0}]),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",input:t+"default.html"})}]).directive("sfFieldset",function(){return{transclude:!0,scope:!0,templateUrl:"directives/decorators/bootstrap/fieldset-trcl.html",link:function(e,t,r){e.title=e.$eval(r.title)}}}); \ No newline at end of file diff --git a/dist/schema-form.min.js b/dist/schema-form.min.js index 369993e23..192062728 100644 --- a/dist/schema-form.min.js +++ b/dist/schema-form.min.js @@ -1 +1 @@ -angular.module("schemaForm",[]),angular.module("schemaForm").provider("schemaFormDecorators",["$compileProvider",function(e){var r="",t={},n=function(e,n){"sfDecorator"===e&&(e=r);for(var a=t[e],i=a.rules,o=0;o',link:function(e,t,n){var a={items:"c",titleMap:"c",schema:"c"},i={type:r},o=!0;angular.forEach(n,function(r,t){if("$"!==t[0]&&0!==t.indexOf("ng")&&"sfField"!==t){var u=function(r){angular.isDefined(r)&&r!==i[t]&&(i[t]=r,o&&i.type&&(i.key||angular.isUndefined(n.key))&&(e.form=i,o=!1))};"model"===t?e.$watch(r,function(r){r&&e.model!==r&&(e.model=r)}):"c"===a[t]?e.$watchCollection(r,u):n.$observe(t,u)}})}}})};this.createDecorator=function(e,n,i){t[e]={mappings:n||{},rules:i||[]},t[r]||(r=e),a(e)},this.createDirective=i,this.createDirectives=function(e){angular.forEach(e,function(e,r){i(r,e)})},this.directive=function(e){return e=e||r,t[e]},this.$get=function(){return{directive:function(e){return t[e]},defaultDecorator:r}},a("sfDecorator")}]),angular.module("schemaForm").factory("schemaForm",[function(){var e={};e.merge=function(r,t,n){t=t||["*"];var a=e.defaults(r,n),i=t.indexOf("*");if(-1!==i)return t=t.slice(0,i).concat(a.form).concat(t.slice(i+1));var o=a.lookup;return t.map(function(t){return"string"==typeof t&&(t={key:t}),t.items&&(t.items=e.merge(r,t.items,n)),t.key&&o[t.key]?angular.extend(o[t.key],t):t})};var r=function(e,r){var t={};return e.title&&(t.title=e.title),e.description&&(t.description=e.description),(r.required===!0||e.required===!0)&&(t.required=!0),e.default&&(t.default=e.default),e.maxLength&&(t.maxlength=e.maxLength),e.minLength&&(t.minlength=e.maxLength),(e.readOnly||e.readonly)&&(t.readonly=e.readOnly||e.readonly),e.minimum&&(t.minimum=e.minimum+(e.exclusiveMinimum?1:0)),e.maximum&&(t.maximum=e.maximum-(e.exclusiveMaximum?1:0)),e.validationMessage&&(t.validationMessage=e.validationMessage),e.enumNames&&(t.titleMap=e.enumNames),t.schema=e,t},t=function(e,t,n){if("string"===t.type&&!t.enum){var a=r(t,n);return a.key=n.path,a.type="text",n.lookup[n.path]=a,a}},n=function(e,t,n){if("number"===t.type){var a=r(t,n);return a.key=n.path,a.type="number",n.lookup[n.path]=a,a}},a=function(e,t,n){if("integer"===t.type){var a=r(t,n);return a.key=n.path,a.type="number",n.lookup[n.path]=a,a}},i=function(e,t,n){if("boolean"===t.type){var a=r(t,n);return a.key=n.path,a.type="checkbox",n.lookup[n.path]=a,a}},o=function(e,t,n){if("string"===t.type&&t.enum){var a=r(t,n);return a.key=n.path,a.type="select",a.titleMap||(a.titleMap={},t.enum.forEach(function(e){a.titleMap[e]=e})),n.lookup[n.path]=a,a}},u=function(e,t,n){if("array"===t.type&&t.items&&t.items.enum){var a=r(t,n);return a.key=n.path,a.type="checkboxes",a.titleMap||(a.titleMap={},t.items.enum.forEach(function(e){a.titleMap[e]=e})),n.lookup[n.path]=a,a}},c=function(e,t,n){if("object"===t.type){var a=r(t,n);return a.type="fieldset",a.items=[],n.lookup[n.path]=a,angular.forEach(t.properties,function(e,r){var i=n.path+"."+r;if(n.ignore[i]!==!0){var o=t.required&&-1!==t.required.indexOf(r),u=f(r,e,{path:i,required:o||!1,lookup:n.lookup,ignore:n.ignore});u&&a.items.push(u)}}),a}},l=[t,o,c,n,a,i,u],f=function(e,r,t){for(var n,a=0;a Date: Wed, 23 Apr 2014 10:50:19 +0200 Subject: [PATCH 10/10] Version bump to 0.0.4 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index f01b764e2..363afd744 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "angular-schema-form", - "version": "0.0.3", + "version": "0.0.4", "authors": [ "Textalk", "David Jensen " diff --git a/package.json b/package.json index 9286cf3a2..0026404e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-schema-form", - "version": "0.0.3", + "version": "0.0.4", "description": "Create forms from a JSON schema", "scripts": { "test": "echo \"Error: no test specified\" && exit 1"