├── .bowerrc ├── app ├── images │ ├── down.png │ └── logo.png ├── app-routes.es6 ├── app-module.es6 ├── builder │ ├── decorators │ │ ├── select-box │ │ │ ├── bootstrap-select-box-decorator.tpl.html │ │ │ └── bootstrap-select-box-decorator.es6 │ │ ├── typehead │ │ │ ├── bootstrap-typehead-decorator.tpl.html │ │ │ └── bootstrap-typehead-decorator.es6 │ │ ├── ace │ │ │ ├── ui-ace.html │ │ │ └── bootstrap-ui-ace.es6 │ │ ├── number │ │ │ ├── bootstrap-decorator.es6 │ │ │ └── bootstrap-number-decorator.tpl.html │ │ ├── simple-array │ │ │ ├── bootstrap-simple-array-decorator.es6 │ │ │ └── bootstrap-simple-array-decorator.tpl.html │ │ ├── datepicker │ │ │ ├── bootstrap-datepicker.es6 │ │ │ └── datepicker.html │ │ └── accordion-array │ │ │ ├── bootstrap-accordion-array-decorator.es6 │ │ │ └── bootstrap-accordion-array-decorator.tpl.html │ ├── builder-routes.es6 │ ├── controllers │ │ ├── builder-controller_test.es6 │ │ └── builder-controller.es6 │ ├── factories │ │ ├── converter-factory_test.es6 │ │ └── converter-factory.es6 │ ├── views │ │ ├── open.tpl.html │ │ └── builder.tpl.html │ └── builder-module.es6 ├── index.html └── scss │ └── builder.scss ├── .eslintrc ├── .editorconfig ├── .yo-rc.json ├── .gitignore ├── README.md ├── .jshintrc ├── protractor.config.js ├── gulp ├── watch.js ├── analyze.js ├── test.js └── build.js ├── bower.json ├── karma.config.js ├── LICENSE ├── Gulpfile.js ├── package.json └── .jscsrc /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "analytics": false, 3 | "directory": "vendor" 4 | } -------------------------------------------------------------------------------- /app/images/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/json-schema-form/json-schema-builder/master/app/images/down.png -------------------------------------------------------------------------------- /app/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/json-schema-form/json-schema-builder/master/app/images/logo.png -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "angular", 4 | "dustinspecker/esnext" 5 | ], 6 | "rules": { 7 | "angular/ng_controller_name": [2, "/[A-Z].*Ctrl$/"], 8 | "consistent-this": 0 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/app-routes.es6: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('schemaFormBuilder') 6 | .config(config); 7 | 8 | function config($urlRouterProvider) { 9 | $urlRouterProvider.otherwise('/builder'); 10 | } 11 | }()); 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /app/app-module.es6: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | /* @ngdoc object 5 | * @name schemaFormBuilder 6 | * @description 7 | * 8 | */ 9 | angular 10 | .module('schemaFormBuilder', [ 11 | 'builder', 12 | 'ui.bootstrap' 13 | ]); 14 | }()); 15 | -------------------------------------------------------------------------------- /app/builder/decorators/select-box/bootstrap-select-box-decorator.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
-------------------------------------------------------------------------------- /app/builder/decorators/typehead/bootstrap-typehead-decorator.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 5 |
-------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-ng-poly": { 3 | "appScript": "es6", 4 | "controllerAs": true, 5 | "directiveTemplateUrl": true, 6 | "e2eTestFramework": "mocha", 7 | "markup": "html", 8 | "ngRoute": false, 9 | "structure": "module-type", 10 | "style": "scss", 11 | "testFramework": "mocha", 12 | "testScript": "es6" 13 | } 14 | } -------------------------------------------------------------------------------- /app/builder/decorators/ace/ui-ace.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | 6 | {{ (hasError() && errorMessage(schemaError())) || form.description}} 7 |
-------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac 2 | .DS_Store 3 | 4 | # Win 5 | Thumbs.db 6 | ehthumbs.db 7 | 8 | #IntelliJ 9 | .idea 10 | 11 | #yeoman 12 | # node_modules 13 | node_modules 14 | report 15 | coverage 16 | build 17 | tmp 18 | 19 | # Log files 20 | *.log 21 | 22 | #bower_components 23 | bower_components 24 | vendor 25 | 26 | 27 | ### Node ### 28 | # Logs 29 | logs 30 | *.log 31 | 32 | # Runtime data 33 | pids 34 | *.pid 35 | *.seed 36 | -------------------------------------------------------------------------------- /app/builder/builder-routes.es6: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('builder') 6 | .config(config); 7 | 8 | function config($stateProvider) { 9 | $stateProvider 10 | .state('builder', { 11 | url: '/builder', 12 | templateUrl: 'builder/views/builder.tpl.html', 13 | controller: 'BuilderCtrl', 14 | controllerAs: 'builder' 15 | }); 16 | } 17 | }()); 18 | -------------------------------------------------------------------------------- /app/builder/controllers/builder-controller_test.es6: -------------------------------------------------------------------------------- 1 | /* global describe, beforeEach, it, expect, inject, module */ 2 | 'use strict'; 3 | 4 | describe('BuilderCtrl', () => { 5 | let ctrl; 6 | 7 | beforeEach(module('builder')); 8 | 9 | beforeEach(inject(($rootScope, $controller) => { 10 | ctrl = $controller('BuilderCtrl'); 11 | })); 12 | 13 | it('should have ctrlName as BuilderCtrl', () => { 14 | expect(ctrl.ctrlName).to.equal('BuilderCtrl'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Json Schema Form Builder 2 | 3 | Quickly generate your your schema form using this GUI builder. 4 | 5 | # Roadmap 6 | 7 | - Complete support of all elements based on schema-form 8 | - Provide source definition for different platforms 9 | - 100% test coverage 10 | 11 | # Demo 12 | There is a live demo at http://ralphowino.github.io/schema-form-builder 13 | 14 | # Simply need a html form? 15 | To generate vanilla/bootstrap/material forms check out ralphowino.github.com/form-builder 16 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": "nofunc", 11 | "laxcomma": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "white": true, 22 | "globals": { 23 | "angular": false 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/builder/decorators/number/bootstrap-decorator.es6: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | /* @ngdoc object 5 | * @name core 6 | * @description 7 | * 8 | */ 9 | angular 10 | .module('builder').config(bootstrapDecorator); 11 | 12 | function bootstrapDecorator(schemaFormDecoratorsProvider) { 13 | schemaFormDecoratorsProvider.addMapping( 14 | 'bootstrapDecorator', 15 | 'number', 16 | 'builder/decorators/number/bootstrap-number-decorator.tpl.html' 17 | ); 18 | } 19 | 20 | }()); 21 | -------------------------------------------------------------------------------- /protractor.config.js: -------------------------------------------------------------------------------- 1 | var glob = require('glob') 2 | , buildConfigFile = require('find-up').sync('build.config.js') 3 | , buildConfig = require(buildConfigFile); 4 | 5 | exports.config = { 6 | baseUrl: 'http://' + buildConfig.host + ':' + buildConfig.port, 7 | seleniumServerJar: glob.sync('./node_modules/protractor/selenium/selenium-server-standalone-*.jar').join(), 8 | capabilities: { 9 | browserName: 'chrome', 10 | chromeOptions: { 11 | args: ['--test-type'] 12 | } 13 | }, 14 | 15 | framework: 'mocha' 16 | }; 17 | -------------------------------------------------------------------------------- /gulp/watch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (gulp, $, config) { 4 | gulp.task('browserSync', function () { 5 | $.browserSync({ 6 | host: config.host, 7 | open: 'external', 8 | port: config.port, 9 | server: { 10 | baseDir: config.buildDir 11 | } 12 | }); 13 | }); 14 | 15 | gulp.task('watch', function () { 16 | $.browserSync.reload(); 17 | gulp.watch([config.unitTestFiles], ['unitTest']); 18 | gulp.watch([config.appFiles, '!' + config.unitTestFiles], ['build', $.browserSync.reload]); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /app/builder/factories/converter-factory_test.es6: -------------------------------------------------------------------------------- 1 | /* global describe, beforeEach, it, expect, inject, module */ 2 | 'use strict'; 3 | 4 | describe('Converter', () => { 5 | let factory; 6 | 7 | beforeEach(module('builder')); 8 | 9 | beforeEach(inject((Converter) => { 10 | factory = Converter; 11 | })); 12 | 13 | it('should have someValue be Converter', () => { 14 | expect(factory.someValue).to.equal('Converter'); 15 | }); 16 | 17 | it('should have someMethod return Converter', () => { 18 | expect(factory.someMethod()).to.equal('Converter'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | schema form builder 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "schema-form-builder", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "~1.4.*", 6 | "angular-bootstrap": "~0.14", 7 | "angular-ui-router": "~0.2.10", 8 | "bootstrap": "~3.2.0", 9 | "font-awesome": "~4.2.0", 10 | "lodash": "~3.9.0", 11 | "angular-schema-form": "~0.8.12", 12 | "angular-locker": "~2.0.4", 13 | "angular-ui-sortable": "~0.13.4", 14 | "angular-growl-v2": "~0.7.9" 15 | }, 16 | "devDependencies": { 17 | "angular-mocks": "~1.4.*" 18 | }, 19 | "overrides": { 20 | "angular": { 21 | "dependencies": { 22 | "jquery": "*" 23 | } 24 | } 25 | }, 26 | "resolutions": { 27 | "angular": "~1.4.*" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/builder/views/open.tpl.html: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /app/builder/decorators/select-box/bootstrap-select-box-decorator.es6: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | /* @ngdoc object 5 | * @name core 6 | * @description 7 | * 8 | */ 9 | angular 10 | .module('builder').config(bootstrapTypeHeadDecorator); 11 | 12 | function bootstrapTypeHeadDecorator(schemaFormProvider, schemaFormDecoratorsProvider, sfPathProvider) { 13 | 14 | 15 | schemaFormDecoratorsProvider.addMapping( 16 | 'bootstrapDecorator', 17 | 'select-box', 18 | 'builder/decorators/select-box/bootstrap-select-box-decorator.tpl.html' 19 | ); 20 | schemaFormDecoratorsProvider.createDirective( 21 | 'select-box', 22 | 'builder/decorators/select-box/bootstrap-select-box-decorator.tpl.html' 23 | ); 24 | } 25 | 26 | }()); 27 | -------------------------------------------------------------------------------- /app/builder/decorators/simple-array/bootstrap-simple-array-decorator.es6: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | /* @ngdoc object 5 | * @name core 6 | * @description 7 | * 8 | */ 9 | angular 10 | .module('builder').config(bootstrapSimpleArrayDecorator); 11 | 12 | function bootstrapSimpleArrayDecorator(schemaFormProvider, schemaFormDecoratorsProvider, sfPathProvider) { 13 | schemaFormDecoratorsProvider.addMapping( 14 | 'bootstrapDecorator', 15 | 'simple-array', 16 | 'builder/decorators/simple-array/bootstrap-simple-array-decorator.tpl.html' 17 | ); 18 | schemaFormDecoratorsProvider.createDirective( 19 | 'simple-array', 20 | 'builder/decorators/simple-array/bootstrap-simple-array-decorator.tpl.html' 21 | ); 22 | } 23 | 24 | }()); 25 | -------------------------------------------------------------------------------- /app/builder/builder-module.es6: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | /* @ngdoc object 5 | * @name builder 6 | * @description 7 | * 8 | */ 9 | angular 10 | .module('builder', [ 11 | 'angular-growl', 12 | 'angular-locker', 13 | 'schemaForm', 14 | 'ui.bootstrap', 15 | 'ui.router', 16 | 'ui.sortable' 17 | ]).config(['lockerProvider', function config(lockerProvider) { 18 | lockerProvider.defaults({ 19 | driver: 'local', 20 | namespace: 'ro', 21 | separator: '.', 22 | eventsEnabled: true, 23 | extend: {} 24 | }); 25 | }]) 26 | .config(['growlProvider', function (growlProvider) { 27 | growlProvider.globalTimeToLive({success: 1000, error: 2000, warning: 3000, info: 4000}); 28 | }]); 29 | }()); 30 | -------------------------------------------------------------------------------- /app/builder/decorators/ace/bootstrap-ui-ace.es6: -------------------------------------------------------------------------------- 1 | angular.module('schemaForm').config( 2 | ['schemaFormProvider', 'schemaFormDecoratorsProvider', 'sfPathProvider', 3 | function (schemaFormProvider, schemaFormDecoratorsProvider, sfPathProvider) { 4 | 5 | var ace = function (name, schema, options) { 6 | if (schema.type === 'ace') { 7 | var f = schemaFormProvider.stdFormObj(name, schema, options); 8 | f.key = options.path; 9 | f.type = 'ace'; 10 | options.lookup[sfPathProvider.stringify(options.path)] = f; 11 | return f; 12 | } 13 | }; 14 | 15 | schemaFormProvider.defaults.string.unshift(ace); 16 | 17 | //Add to the bootstrap directive 18 | schemaFormDecoratorsProvider.addMapping('bootstrapDecorator', 'ace', 19 | 'builder/decorators/ace/ui-ace.html'); 20 | schemaFormDecoratorsProvider.createDirective('ace', 21 | 'builder/decorators/ace/ui-ace.html'); 22 | }]); -------------------------------------------------------------------------------- /karma.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var buildConfig = require('./build.config.js') 3 | , preprocessors = {} 4 | , buildTestDir 5 | , templateDir 6 | , jsDir; 7 | 8 | buildTestDir = buildConfig.buildTestDir; 9 | // add slash if missing to properly strip prefix from directive templates 10 | if (buildTestDir[buildTestDir.length - 1] !== '/') { 11 | buildTestDir = buildTestDir + '/'; 12 | } 13 | templateDir = buildTestDir + 'templates/'; 14 | 15 | jsDir = buildConfig.buildJs; 16 | // add slash if missing to properly strip prefix from directive templates 17 | if (jsDir[jsDir.length - 1] !== '/') { 18 | jsDir = jsDir + '/'; 19 | } 20 | 21 | preprocessors[jsDir + '**/*.js'] = ['coverage']; 22 | preprocessors[templateDir + '**/*-directive.tpl.html'] = ['ng-html2js']; 23 | 24 | module.exports = { 25 | browsers: ['PhantomJS'], 26 | frameworks: ['mocha', 'chai', 'sinon'], 27 | reporters: ['failed', 'coverage'], 28 | preprocessors: preprocessors, 29 | ngHtml2JsPreprocessor: { 30 | stripPrefix: templateDir 31 | }, 32 | singleRun: true 33 | }; 34 | -------------------------------------------------------------------------------- /app/builder/decorators/datepicker/bootstrap-datepicker.es6: -------------------------------------------------------------------------------- 1 | angular.module('schemaForm').config( 2 | ['schemaFormProvider', 'schemaFormDecoratorsProvider', 'sfPathProvider', 3 | function(schemaFormProvider, schemaFormDecoratorsProvider, sfPathProvider) { 4 | 5 | var datepicker = function(name, schema, options) { 6 | if (schema.type === 'string' && (schema.format === 'date')) { 7 | var f = schemaFormProvider.stdFormObj(name, schema, options); 8 | f.key = options.path; 9 | f.type = 'datepicker'; 10 | options.lookup[sfPathProvider.stringify(options.path)] = f; 11 | return f; 12 | } 13 | }; 14 | 15 | schemaFormProvider.defaults.string.unshift(datepicker); 16 | 17 | //Add to the bootstrap directive 18 | schemaFormDecoratorsProvider.addMapping( 19 | 'bootstrapDecorator', 20 | 'datepicker', 21 | 'builder/decorators/datepicker/datepicker.html' 22 | ); 23 | schemaFormDecoratorsProvider.createDirective( 24 | 'datepicker', 25 | 'builder/decorators/datepicker/datepicker.html' 26 | ); 27 | } 28 | ]); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ralphowino Consulting 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/builder/decorators/typehead/bootstrap-typehead-decorator.es6: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | /* @ngdoc object 5 | * @name core 6 | * @description 7 | * 8 | */ 9 | angular 10 | .module('builder').config(bootstrapTypeHeadDecorator); 11 | 12 | function bootstrapTypeHeadDecorator(schemaFormProvider, schemaFormDecoratorsProvider, sfPathProvider) { 13 | 14 | var typehead = function (name, schema, options) { 15 | var form; 16 | if (schema.type === 'typehead' || schema.type['x-schema-form'] === 'typehead' || (!_.isUndefined(form) && form.type === 'typehead')) { 17 | form = schemaFormProvider.stdFormObj(name, schema, options); 18 | options.lookup[sfPathProvider.stringify(options.path)] = form; 19 | return form; 20 | } 21 | }; 22 | 23 | schemaFormProvider.defaults.string.unshift(typehead); 24 | 25 | schemaFormDecoratorsProvider.addMapping( 26 | 'bootstrapDecorator', 27 | 'typehead', 28 | 'builder/decorators/typehead/bootstrap-typehead-decorator.tpl.html' 29 | ); 30 | schemaFormDecoratorsProvider.createDirective( 31 | 'typehead', 32 | 'builder/decorators/typehead/bootstrap-typehead-decorator.tpl.html' 33 | ); 34 | } 35 | 36 | }()); 37 | -------------------------------------------------------------------------------- /app/builder/decorators/accordion-array/bootstrap-accordion-array-decorator.es6: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | /* @ngdoc object 5 | * @name core 6 | * @description 7 | * 8 | */ 9 | angular 10 | .module('builder').config(bootstrapAccordionArrayDecorator); 11 | 12 | function bootstrapAccordionArrayDecorator(schemaFormProvider, schemaFormDecoratorsProvider, sfPathProvider) { 13 | 14 | //var accordion = function (name, schema, options) { 15 | // var f = schemaFormProvider.stdFormObj(name, schema, options); 16 | // 17 | // f.openLast = function(){ 18 | // alert('open'); 19 | // }; 20 | // options.lookup[sfPathProvider.stringify(options.path)] = f; 21 | // return f; 22 | //}; 23 | // 24 | //schemaFormProvider.defaults.array.unshift(accordion); 25 | 26 | schemaFormDecoratorsProvider.addMapping( 27 | 'bootstrapDecorator', 28 | 'accordion-array', 29 | 'builder/decorators/accordion-array/bootstrap-accordion-array-decorator.tpl.html' 30 | ); 31 | schemaFormDecoratorsProvider.createDirective( 32 | 'accordion-array', 33 | 'builder/decorators/accordion-array/bootstrap-accordion-array-decorator.tpl.html' 34 | ); 35 | } 36 | 37 | }()); 38 | -------------------------------------------------------------------------------- /app/builder/decorators/simple-array/bootstrap-simple-array-decorator.tpl.html: -------------------------------------------------------------------------------- 1 |
3 |
    4 |
  1. 5 | 8 | 9 |
  2. 10 |
11 |
12 | 16 |
17 |
19 |
-------------------------------------------------------------------------------- /app/builder/decorators/datepicker/datepicker.html: -------------------------------------------------------------------------------- 1 |
3 | 5 | 6 |

7 | 8 | 21 | 22 | 24 | 25 |

26 | {{ (hasError() && errorMessage(schemaError())) || form.description}} 27 |
-------------------------------------------------------------------------------- /app/builder/decorators/accordion-array/bootstrap-accordion-array-decorator.tpl.html: -------------------------------------------------------------------------------- 1 |
3 | 4 | 5 | 6 | {{interp(form.title,{'$index':$index, value: item}) || $index}} 7 | 8 | 9 | 14 | 15 | 16 |
17 | 21 |
22 |
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /gulp/analyze.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (gulp, $, config) { 4 | // lint source code 5 | gulp.task('lint', function () { 6 | var es6Filter = $.filter('**/*.es6', {restore: true}); 7 | 8 | return gulp.src([ 9 | config.appScriptFiles, 10 | config.e2eFiles, 11 | config.unitTestFiles 12 | ]) 13 | .pipe($.plumber({errorHandler: function (err) { 14 | //$.notify.onError({ 15 | // title: 'Error linting at ' + err.plugin, 16 | // subtitle: ' ', //overrides defaults 17 | // message: err.message.replace(/\u001b\[.*?m/g, ''), 18 | // sound: ' ' //overrides defaults 19 | //})(err); 20 | 21 | this.emit('end'); 22 | }})) 23 | .pipe(es6Filter) 24 | .pipe($.eslint()) 25 | .pipe($.eslint.formatEach('./node_modules/eslint-path-formatter')) 26 | .pipe($.eslint.failOnError()) 27 | .pipe($.jshint()) 28 | .pipe($.jshint.reporter('jshint-stylish')) 29 | .pipe($.jshint.reporter('fail')) 30 | .pipe($.jscs({ 31 | esnext: true 32 | })); 33 | }); 34 | 35 | // run plato anaylysis on JavaScript (ES5) files 36 | gulp.task('staticAnalysis', function (done) { 37 | $.multiGlob.glob([config.appScriptFiles, config.e2eFiles, config.unitTestFiles], function (err, matches) { 38 | if (err) { 39 | throw new Error('Couldn\'t find files.'); 40 | } 41 | 42 | // only inspect JS (ES5) files 43 | matches = matches.filter(function (file) { 44 | return file.match(/.*[.]js/); 45 | }); 46 | 47 | if (matches.length > 0) { 48 | $.plato.inspect(matches, './report', {}, function () { 49 | done(); 50 | }); 51 | } else { 52 | done(); 53 | } 54 | }); 55 | }); 56 | 57 | gulp.task('analyze', ['lint', 'staticAnalysis']); 58 | }; 59 | -------------------------------------------------------------------------------- /Gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash') 4 | , buildConfig = require('./build.config') 5 | , config = {} 6 | , gulp = require('gulp') 7 | , gulpFiles = require('require-dir')('./gulp') 8 | , path = require('path') 9 | , $, key; 10 | 11 | $ = require('gulp-load-plugins')({ 12 | pattern: [ 13 | 'browser-sync', 14 | 'del', 15 | 'gulp-*', 16 | 'karma', 17 | 'main-bower-files', 18 | 'multi-glob', 19 | 'plato', 20 | 'run-sequence', 21 | 'streamqueue', 22 | 'uglify-save-license', 23 | 'wiredep', 24 | 'yargs' 25 | ] 26 | }); 27 | 28 | _.merge(config, buildConfig); 29 | 30 | config.appFiles = path.join(config.appDir, '**/*'); 31 | config.appFontFiles = path.join(config.appDir, 'fonts/**/*'); 32 | config.appImageFiles = path.join(config.appDir, 'images/**/*'); 33 | config.appMarkupFiles = path.join(config.appDir, '**/*.html'); 34 | config.appScriptFiles = path.join(config.appDir, '**/*.es6'); 35 | config.appStyleFiles = path.join(config.appDir, '**/*.scss'); 36 | 37 | config.buildDirectiveTemplateFiles = path.join(config.buildDir, '**/*directive.tpl.html'); 38 | config.buildJsFiles = path.join(config.buildJs, '**/*.js'); 39 | 40 | config.buildTestDirectiveTemplateFiles = path.join(config.buildTestDir, '**/*directive.tpl.html'); 41 | config.buildE2eTestsDir = path.join(config.buildTestDir, 'e2e'); 42 | config.buildE2eTests = path.join(config.buildE2eTestsDir, '**/*_test.js'); 43 | config.buildTestDirectiveTemplatesDir = path.join(config.buildTestDir, 'templates'); 44 | config.buildUnitTestsDir = path.join(config.buildTestDir, config.unitTestDir); 45 | config.buildUnitTestFiles = path.join(config.buildUnitTestsDir, '**/*_test.js'); 46 | 47 | config.e2eFiles = path.join('e2e', '**/*.es6'); 48 | config.unitTestFiles = path.join(config.unitTestDir, '**/*_test.es6'); 49 | 50 | for (key in gulpFiles) { 51 | gulpFiles[key](gulp, $, config); 52 | } 53 | 54 | gulp.task('dev', ['build'], function () { 55 | gulp.start('browserSync'); 56 | gulp.start('watch'); 57 | }); 58 | 59 | gulp.task('default', ['dev']); 60 | -------------------------------------------------------------------------------- /app/builder/decorators/number/bootstrap-number-decorator.tpl.html: -------------------------------------------------------------------------------- 1 |
3 | 5 | 10 |
12 | 13 | 18 | 19 |
20 | {{ hasSuccess() ? '(success)' : '(error)' }} 25 |
26 |
-------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "schema-form-builder", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "test": "gulp test" 6 | }, 7 | "dependencies": {}, 8 | "devDependencies": { 9 | "browser-sync": "^2.2.3", 10 | "chai": "^2.1.1", 11 | "chai-as-promised": "^4.1.1", 12 | "del": "^2.0.0", 13 | "eslint-config-angular": "^0.2.0", 14 | "eslint-config-dustinspecker": "^0.1.0", 15 | "eslint-path-formatter": "^0.1.1", 16 | "eslint-plugin-angular": "^0.2.0", 17 | "find-up": "^1.0.0", 18 | "glob": "^5.0.1", 19 | "gulp": "^3.8.7", 20 | "gulp-angular-filesort": "^1.0.4", 21 | "gulp-autoprefixer": "^2.0.0", 22 | "gulp-babel": "^5.0.0", 23 | "gulp-concat": "^2.3.4", 24 | "gulp-css-rebase-urls": "0.0.5", 25 | "gulp-cssmin": "^0.1.6", 26 | "gulp-eslint": "^1.0.0", 27 | "gulp-filter": "^3.0.0", 28 | "gulp-htmlmin": "^1.0.0", 29 | "gulp-if": "^1.2.4", 30 | "gulp-imagemin": "^2.0.0", 31 | "gulp-inject": "^1.5.0", 32 | "gulp-jscs": "^3.0.0", 33 | "gulp-jshint": "^1.8.4", 34 | "gulp-load-plugins": "^0.10.0", 35 | "gulp-modify-css-urls": "^0.2.0", 36 | "gulp-ng-annotate": "^1.1.0", 37 | "gulp-ng-html2js": "^0.2.0", 38 | "gulp-notify": "^2.1.0", 39 | "gulp-order": "^1.1.1", 40 | "gulp-plumber": "^1.0.0", 41 | "gulp-protractor": "^1.0.0", 42 | "gulp-rename": "^1.2.0", 43 | "gulp-rev": "^5.1.0", 44 | "gulp-sass": "^2.0.0", 45 | "gulp-sourcemaps": "^1.5.1", 46 | "gulp-tslint": "^3.1.2", 47 | "gulp-uglify": "^1.0.0", 48 | "jshint-stylish": "^2.0.1", 49 | "karma": "^0.13.3", 50 | "karma-chai": "^0.1.0", 51 | "karma-coverage": "^0.4.2", 52 | "karma-failed-reporter": "^0.0.3", 53 | "karma-mocha": "^0.1.9", 54 | "karma-ng-html2js-preprocessor": "^0.1.0", 55 | "karma-phantomjs-launcher": "^0.2.0", 56 | "karma-sinon": "^1.0.3", 57 | "lodash": "^3.6.0", 58 | "main-bower-files": "^2.0.0", 59 | "mocha": "^2.2.0", 60 | "multi-glob": "^0.4.0", 61 | "node-sass": "^3.4.2", 62 | "phantomjs": "^1.9.18", 63 | "plato": "^1.4.0", 64 | "protractor": "^2.0.0", 65 | "require-dir": "^0.3.0", 66 | "run-sequence": "^1.0.2", 67 | "sinon": "^1.17.2", 68 | "streamqueue": "^1.1.0", 69 | "uglify-save-license": "^0.4.1", 70 | "underscore.string": "^3.0.1", 71 | "wiredep": "^2.2.0", 72 | "yargs": "^3.4.5" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /gulp/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var karmaConf = require('../karma.config.js'); 4 | 5 | // karmaConf.files get populated in karmaFiles 6 | karmaConf.files = [ 7 | 'node_modules/karma-babel-preprocessor/node_modules/babel-core/browser-polyfill.js' 8 | ]; 9 | 10 | module.exports = function (gulp, $, config) { 11 | gulp.task('clean:test', function (cb) { 12 | return $.del(config.buildTestDir, cb); 13 | }); 14 | 15 | gulp.task('buildTests', ['lint', 'clean:test'], function () { 16 | return gulp.src([config.unitTestFiles]) 17 | .pipe($.babel()) 18 | .pipe($.rename(function (filePath) { 19 | filePath.extname = '.js'; 20 | })) 21 | .pipe(gulp.dest(config.buildUnitTestsDir)); 22 | }); 23 | 24 | // inject scripts in karma.config.js 25 | gulp.task('karmaFiles', ['build', 'buildTests'], function () { 26 | var stream = $.streamqueue({objectMode: true}); 27 | 28 | // add bower javascript 29 | stream.queue(gulp.src($.wiredep({ 30 | devDependencies: true 31 | }).js)); 32 | 33 | // add application templates 34 | stream.queue(gulp.src([config.buildTestDirectiveTemplateFiles])); 35 | 36 | // add application javascript 37 | stream.queue(gulp.src([ 38 | config.buildJsFiles, 39 | '!**/*_test.*' 40 | ]) 41 | .pipe($.angularFilesort())); 42 | 43 | // add unit tests 44 | stream.queue(gulp.src([config.buildUnitTestFiles])); 45 | 46 | return stream.done() 47 | .on('data', function (file) { 48 | karmaConf.files.push(file.path); 49 | }); 50 | }); 51 | 52 | // run unit tests 53 | gulp.task('unitTest', ['lint', 'karmaFiles'], function (done) { 54 | var server = new $.karma.Server(karmaConf, done); 55 | server.start(); 56 | }); 57 | 58 | gulp.task('build:e2eTest', function () { 59 | return gulp.src([config.e2eFiles]) 60 | .pipe($.babel()) 61 | .pipe($.rename(function (filePath) { 62 | filePath.extname = '.js'; 63 | })) 64 | .pipe(gulp.dest(config.buildE2eTestsDir)); 65 | }); 66 | 67 | // run e2e tests - SERVER MUST BE RUNNING FIRST 68 | gulp.task('e2eTest', ['lint', 'build:e2eTest'], function () { 69 | return gulp.src(config.buildE2eTests) 70 | .pipe($.protractor.protractor({ 71 | configFile: 'protractor.config.js' 72 | })) 73 | .on('error', function (e) { 74 | console.log(e); 75 | }); 76 | }); 77 | 78 | // jscs:disable requireCamelCaseOrUpperCaseIdentifiers 79 | /* jshint -W106 */ 80 | gulp.task('webdriverUpdate', $.protractor.webdriver_update); 81 | /* jshint +W106 */ 82 | // jscs:enable requireCamelCaseOrUpperCaseIdentifiers 83 | }; 84 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "disallowDanglingUnderscores": true, 3 | "disallowEmptyBlocks": true, 4 | "disallowKeywords": [ 5 | "with" 6 | ], 7 | "disallowKeywordsOnNewLine": [ 8 | "else" 9 | ], 10 | "disallowMixedSpacesAndTabs": true, 11 | "disallowMultipleLineBreaks": true, 12 | "disallowMultipleLineStrings": true, 13 | "disallowQuotedKeysInObjects": true, 14 | "disallowSpaceAfterObjectKeys": true, 15 | "disallowSpaceAfterPrefixUnaryOperators": true, 16 | "disallowSpaceBeforeBinaryOperators": [ 17 | "," 18 | ], 19 | "disallowSpaceBeforePostfixUnaryOperators": true, 20 | "disallowSpacesInsideArrayBrackets": "all", 21 | "disallowSpacesInsideObjectBrackets": "all", 22 | "disallowSpacesInsideParentheses": true, 23 | "disallowTrailingComma": true, 24 | "disallowTrailingWhitespace": true, 25 | "disallowYodaConditions": true, 26 | "maximumLineLength": { 27 | "allowComments": true, 28 | "allowRegex": true, 29 | "allowUrlComments": true, 30 | "value": 120 31 | }, 32 | "requireBlocksOnNewline": 1, 33 | "requireCapitalizedConstructors": true, 34 | "requireCamelCaseOrUpperCaseIdentifiers": true, 35 | "requireCurlyBraces": [ 36 | "if", 37 | "else", 38 | "for", 39 | "while", 40 | "do", 41 | "try", 42 | "catch" 43 | ], 44 | "requireDotNotation": true, 45 | "requireLineFeedAtFileEnd": true, 46 | "requireMultipleVarDecl": "onevar", 47 | "requireOperatorBeforeLineBreak": [ 48 | "?", 49 | "=", 50 | "+", 51 | "-", 52 | "/", 53 | "*", 54 | "==", 55 | "===", 56 | "!=", 57 | "!==", 58 | ">", 59 | ">=", 60 | "<", 61 | "<=" 62 | ], 63 | "requireParenthesesAroundIIFE": true, 64 | "requireSpaceAfterBinaryOperators": true, 65 | "requireSpaceAfterKeywords": [ 66 | "if", 67 | "else", 68 | "for", 69 | "while", 70 | "do", 71 | "switch", 72 | "case", 73 | "return", 74 | "try", 75 | "catch", 76 | "function", 77 | "typeof" 78 | ], 79 | "requireSpaceBeforeBinaryOperators": [ 80 | "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", 81 | "&=", "|=", "^=", "+=", 82 | 83 | "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&", 84 | "|", "^", "&&", "||", "===", "==", ">=", 85 | "<=", "<", ">", "!=", "!==" 86 | ], 87 | "requireSpaceBeforeBlockStatements": true, 88 | "requireSpacesInAnonymousFunctionExpression": { 89 | "beforeOpeningCurlyBrace": true 90 | }, 91 | "requireSpacesInConditionalExpression": true, 92 | "requireSpacesInFunctionExpression": { 93 | "beforeOpeningCurlyBrace": true 94 | }, 95 | "requireSpacesInNamedFunctionExpression": { 96 | "beforeOpeningCurlyBrace": true 97 | }, 98 | "validateIndentation": 2, 99 | "validateQuoteMarks": "'" 100 | } -------------------------------------------------------------------------------- /app/builder/views/builder.tpl.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |

5 | 6 | Schema Form Builder / {{ builder.model.name || 'Untitled Form'}} 7 | New Form 8 | My previous Forms 9 |

10 |
11 |
12 | 13 | 14 |
15 |
16 |
17 |
19 |
20 |
21 |
22 |

preview

23 |
25 |
26 |
27 |
28 |
29 |
30 |

Instructions

32 |

In your angular project do the following

33 |

First, Install angular-schema form

34 |

Using bower:

35 |
bower install angular-schema-form
36 |

Using npm:

37 |
npm install angular-schema-form

38 |

Then add it as a dependency to your module

39 |
angular.module('myModule', ['schemaForm'])
40 |
41 |

Secondly, Specify your schema and form

42 |

In your controller, add the following schema and form an pass it to your views

43 |
44 |
45 |
Schema
46 |
 {{ builder.output.schema | json }}
47 |
48 |
49 |
Forms
50 |
 {{ builder.output.form | json }}
51 |
52 |
53 |
54 |

Lastly, Display the form in your view

55 |
56 |                         <form name="myForm" ng-submit="submit()"> 
57 | <div sf-schema="schema" sf-form="form" sf-model="model"></div>
58 | <input type="submit" value="Submit">
59 | <button type="button" ng-click="goBack()">Cancel</button>
60 | </form> 61 |
62 |
63 |
64 |

To be created

65 |
 {{ builder.output | json }}
66 |
67 | 68 |
69 |
70 |
71 |
-------------------------------------------------------------------------------- /app/builder/factories/converter-factory.es6: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | /** 5 | * @ngdoc service 6 | * @name builder.factory:Converter 7 | * 8 | * @description 9 | * 10 | */ 11 | angular 12 | .module('builder') 13 | .factory('Converter', Converter); 14 | 15 | function Converter() { 16 | let ConverterBase = {}; 17 | ConverterBase.generateStandardAttributes = (field, output) => { 18 | 19 | let form, formProps = [ 20 | 'key', 21 | 'condition', 22 | 'onChange', 23 | 'notitle', 24 | 'validationMessage', 25 | 'onChange', 26 | 'ngModelOptions', 27 | 'condition', 28 | 'fieldAddonLeft', 29 | 'fieldAddonRight', 30 | 'description', 31 | 'showAdvanced', 32 | 'validationMessage', 33 | 'onChange', 34 | 'feedback', 35 | 'disableSuccessState', 36 | 'disableErrorState', 37 | 'placeholder', 38 | 'ngModelOptions', 39 | 'readonly', 40 | 'htmlClass', 41 | 'destroyStrategy', 42 | 'copyValueTo', 43 | 'fieldHtmlClass', 44 | 'labelHtmlClass' 45 | ]; 46 | output.schema.properties[field.key] = { 47 | title: field.title, 48 | description: field.description 49 | }; 50 | 51 | form = _.pick(field, formProps); 52 | output.form.push(form); 53 | return output; 54 | }; 55 | 56 | ConverterBase.updateForm = (output, key, attr, value) => { 57 | let index = _.findKey(output.form, 'key', key); 58 | output.form[index][attr] = value; 59 | return output; 60 | } 61 | 62 | ConverterBase.generateTextField = (field, output) => { 63 | output.schema.properties[field.key]['type'] = 'string'; 64 | return output; 65 | }; 66 | ConverterBase.generateTextareaField = (field, output) => { 67 | output.schema.properties[field.key]['type'] = 'string'; 68 | output = ConverterBase.updateForm(output, field.key, 'type', 'textarea'); 69 | return output; 70 | }; 71 | ConverterBase.generateEmailField = (field, output) => { 72 | output.schema.properties[field.key]['type'] = 'string'; 73 | output.schema.properties[field.key]['format'] = 'email'; 74 | return output; 75 | }; 76 | ConverterBase.generateDropdownField = (field, output) => { 77 | output.schema.properties[field.key]['type'] = 'string'; 78 | output.schema.properties[field.key]['enum'] = field.options; 79 | return output; 80 | }; 81 | ConverterBase.generateDateField = (field, output) => { 82 | output.schema.properties[field.key]['type'] = 'string'; 83 | output.schema.properties[field.key]['format'] = 'date'; 84 | return output; 85 | }; 86 | ConverterBase.generateTimeField = (field, output) => { 87 | output.schema.properties[field.key]['type'] = 'string'; 88 | output.schema.properties[field.key]['format'] = 'time'; 89 | return output; 90 | }; 91 | ConverterBase.generateDateTimeField = (field, output) => { 92 | output.schema.properties[field.key]['type'] = 'string'; 93 | output.schema.properties[field.key]['format'] = 'date-time'; 94 | return output; 95 | }; 96 | 97 | let buildSegment = (field, output) => { 98 | let builder = _.camelCase('generate_' + field.type.replace('-', '_') + '_field'); 99 | output = ConverterBase.generateStandardAttributes(field, output); 100 | if (typeof ConverterBase[builder] === 'function') 101 | return ConverterBase[builder](field, output); 102 | output.schema.properties[field.key] = { 103 | type: field.type, 104 | title: field.title 105 | }; 106 | return output; 107 | }; 108 | 109 | ConverterBase.generateFields = (model) => { 110 | let output = {schema: {type: 'object', properties: {}}, form: []}; 111 | if (_.isArray(model.fields)) { 112 | model.fields.forEach((field) => { 113 | if (!_.isUndefined(field.type) && !_.isUndefined(field.key)) 114 | output = buildSegment(field, output) 115 | } 116 | ); 117 | } 118 | return output; 119 | }; 120 | return ConverterBase; 121 | } 122 | }()); 123 | -------------------------------------------------------------------------------- /app/scss/builder.scss: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400,300,600); 2 | body{ 3 | font-family: 'Open Sans', sans-serif; 4 | color:#989898; 5 | h1, h2, h3, h4{ 6 | font-weight:300; 7 | } 8 | } 9 | .body-section{ 10 | .header{ 11 | width:100%; 12 | padding:20px 15px; 13 | background:#000; 14 | h4{ 15 | color:#fff; 16 | img{ 17 | width: 80px; 18 | margin-top: -20px; 19 | margin-bottom: -10px; 20 | margin-right: 20px; 21 | } 22 | } 23 | } 24 | .container{ 25 | width:100%; 26 | h1, h2, h3, h4{ 27 | color:#000; 28 | font-weight:600; 29 | text-transform: uppercase; 30 | } 31 | .builder{ 32 | height:calc(100vh - 80px); 33 | border-right:1px solid #ccc; 34 | overflow-y:auto; 35 | .my-field{ 36 | border-bottom:0; 37 | border-top:1px solid #ccc; 38 | padding-top:20px; 39 | padding-bottom:20px; 40 | .form-group{ 41 | margin-top:0; 42 | margin-bottom:0; 43 | } 44 | h4{ 45 | margin:0; 46 | } 47 | } 48 | .main-info{ 49 | margin-top:20px; 50 | .field-name{ 51 | border:0; 52 | border-bottom:2px solid #2ECEE9; 53 | color:#2ECEE9; 54 | box-shadow:none; 55 | border-radius: 0 !important; 56 | padding-left:0; 57 | } 58 | .output-type-label{ 59 | color:#2ECEE9; 60 | font-weight:300; 61 | width:40%; 62 | float:left; 63 | text-align: right; 64 | margin-top: 7px; 65 | } 66 | .output-type-label:after{ 67 | content: ' :'; 68 | } 69 | .output-type{ 70 | border:0; 71 | border-bottom:2px solid #2ECEE9; 72 | box-shadow:none; 73 | border-radius: 0 !important; 74 | width:60%; 75 | } 76 | } 77 | 78 | .btn-default{ 79 | width:100%; 80 | border:1px #979797 dashed; 81 | text-transform: uppercase; 82 | color:#979797; 83 | } 84 | .panel-default{ 85 | border:0 !important; 86 | border-radius:5px; 87 | .panel-heading { 88 | background-color: #85E5F5; 89 | border:0; 90 | border-radius:5px; 91 | h4{ 92 | color: #fff; 93 | font-weight:400; 94 | font-size:1em; 95 | text-transform:capitalize; 96 | a{ 97 | color: #fff; 98 | span{ 99 | color: #fff; 100 | } 101 | } 102 | } 103 | } 104 | } 105 | .panel-default.panel-open{ 106 | border:1px solid #FF8500 !important; 107 | border-radius:5px; 108 | .panel-heading{ 109 | background-color: #FF8500; 110 | border-radius:5px 5px 0 0; 111 | } 112 | } 113 | } 114 | .preview{ 115 | background: #eee; 116 | height:calc(100vh - 80px); 117 | overflow-y:auto; 118 | input, textarea{ 119 | background:none; 120 | } 121 | } 122 | } 123 | .instructions{ 124 | display:none; 125 | width:100%; 126 | height:calc(100vh - 80px); 127 | background:#eee; 128 | position: absolute; 129 | top:80px; 130 | left:0; 131 | overflow-y:auto; 132 | z-index:2; 133 | } 134 | .instructions.visible{ 135 | display: block; 136 | } 137 | } 138 | 139 | .hide-label{ 140 | label{ 141 | display:none; 142 | } 143 | .form-control-feedback{ 144 | top:0px; 145 | } 146 | } 147 | .has-success{ 148 | .form-control-feedback { 149 | color: #00E77D !important; 150 | } 151 | .form-control{ 152 | border-color:#00E77D !important; 153 | } 154 | } 155 | .builder{ 156 | .row{ 157 | .col-sm-6{ 158 | .form-control-feedback{ 159 | right:15px; 160 | } 161 | } 162 | } 163 | } 164 | 165 | .modal-content{ 166 | overflow:hidden; 167 | .modal-body{ 168 | .table{ 169 | tr{ 170 | td{ 171 | border-top:0 !important; 172 | } 173 | } 174 | } 175 | } 176 | .modal-footer{ 177 | background: #000; 178 | } 179 | } 180 | 181 | //helpers 182 | .margin-right-20{ 183 | margin-right:20px; 184 | } 185 | strong{ 186 | font-weight:600; 187 | } 188 | .btn{ 189 | border:0; 190 | } 191 | .grey{ 192 | color:#555; 193 | } 194 | .hidden{ 195 | display: none; 196 | } 197 | .bottom-spaced{ 198 | margin-bottom:15px; 199 | } 200 | .btn-success{ 201 | background-color: #00E77D; 202 | border:0; 203 | } 204 | .separated{ 205 | width:100%; 206 | clear:both; 207 | height:50px; 208 | } 209 | 210 | select { 211 | background:url(../../images/down.png) no-repeat 97% !important; 212 | -webkit-box-sizing: border-box; 213 | -moz-box-sizing: border-box; 214 | box-sizing: border-box; 215 | -webkit-appearance:none; 216 | -moz-appearance:none; 217 | } -------------------------------------------------------------------------------- /gulp/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('underscore.string') 4 | , fs = require('fs') 5 | , path = require('path') 6 | 7 | , bowerDir = JSON.parse(fs.readFileSync('.bowerrc')).directory + path.sep; 8 | 9 | module.exports = function (gulp, $, config) { 10 | var isProd = $.yargs.argv.stage === 'prod'; 11 | 12 | // delete build directory 13 | gulp.task('clean', function () { 14 | return $.del(config.buildDir); 15 | }); 16 | 17 | // compile markup files and copy into build directory 18 | gulp.task('markup', ['clean'], function () { 19 | return gulp.src([ 20 | config.appMarkupFiles 21 | ]) 22 | .pipe(gulp.dest(config.buildDir)); 23 | }); 24 | 25 | // compile styles and copy into build directory 26 | gulp.task('styles', ['clean'], function () { 27 | return gulp.src([ 28 | config.appStyleFiles 29 | ]) 30 | .pipe($.plumber({errorHandler: function (err) { 31 | $.notify.onError({ 32 | title: 'Error linting at ' + err.plugin, 33 | subtitle: ' ', //overrides defaults 34 | message: err.message.replace(/\u001b\[.*?m/g, ''), 35 | sound: ' ' //overrides defaults 36 | })(err); 37 | 38 | this.emit('end'); 39 | }})) 40 | .pipe($.sass()) 41 | .pipe($.autoprefixer()) 42 | .pipe($.if(isProd, $.cssRebaseUrls())) 43 | .pipe($.if(isProd, $.modifyCssUrls({ 44 | modify: function (url) { 45 | // determine if url is using http, https, or data protocol 46 | // cssRebaseUrls rebases these URLs, too, so we need to fix that 47 | var beginUrl = url.indexOf('http:'); 48 | if (beginUrl < 0) { 49 | beginUrl = url.indexOf('https:'); 50 | } 51 | if (beginUrl < 0) { 52 | beginUrl = url.indexOf('data:'); 53 | } 54 | 55 | if (beginUrl > -1) { 56 | return url.substring(beginUrl, url.length); 57 | } 58 | 59 | // prepend all other urls 60 | return '../' + url; 61 | } 62 | }))) 63 | .pipe($.if(isProd, $.concat('app.css'))) 64 | .pipe($.if(isProd, $.cssmin())) 65 | .pipe($.if(isProd, $.rev())) 66 | .pipe(gulp.dest(config.buildCss)); 67 | }); 68 | 69 | // compile scripts and copy into build directory 70 | gulp.task('scripts', ['clean', 'analyze', 'markup'], function () { 71 | var es6Filter = $.filter('**/*.es6', {restore: true}) 72 | , htmlFilter = $.filter('**/*.html', {restore: true}) 73 | , jsFilter = $.filter('**/*.js', {restore: true}); 74 | 75 | return gulp.src([ 76 | config.appScriptFiles, 77 | config.buildDir + '**/*.html', 78 | '!**/*_test.*', 79 | '!**/index.html' 80 | ]) 81 | .pipe($.sourcemaps.init()) 82 | .pipe(es6Filter) 83 | .pipe($.babel()) 84 | .pipe($.rename(function (filePath) { 85 | filePath.extname = '.js'; 86 | })) 87 | .pipe(es6Filter.restore) 88 | .pipe($.if(isProd, htmlFilter)) 89 | .pipe($.if(isProd, $.ngHtml2js({ 90 | // lower camel case all app names 91 | moduleName: _.camelize(_.slugify(_.humanize(require('../package.json').name))), 92 | declareModule: false 93 | }))) 94 | .pipe($.if(isProd, htmlFilter.restore)) 95 | .pipe(jsFilter) 96 | .pipe($.if(isProd, $.angularFilesort())) 97 | .pipe($.if(isProd, $.concat('app.js'))) 98 | .pipe($.if(isProd, $.ngAnnotate())) 99 | .pipe($.if(isProd, $.uglify())) 100 | .pipe($.if(isProd, $.rev())) 101 | .pipe($.sourcemaps.write('.')) 102 | .pipe(gulp.dest(config.buildJs)) 103 | .pipe(jsFilter.restore); 104 | }); 105 | 106 | // inject custom CSS and JavaScript into index.html 107 | gulp.task('inject', ['markup', 'styles', 'scripts'], function () { 108 | var jsFilter = $.filter('**/*.js', {restore: true}); 109 | 110 | return gulp.src(config.buildDir + 'index.html') 111 | .pipe($.inject(gulp.src([ 112 | config.buildCss + '**/*', 113 | config.buildJs + '**/*' 114 | ]) 115 | .pipe(jsFilter) 116 | .pipe($.angularFilesort()) 117 | .pipe(jsFilter.restore), { 118 | addRootSlash: false, 119 | ignorePath: config.buildDir 120 | }) 121 | ) 122 | .pipe(gulp.dest(config.buildDir)); 123 | }); 124 | 125 | // copy bower components into build directory 126 | gulp.task('bowerCopy', ['inject'], function () { 127 | var cssFilter = $.filter('**/*.css', {restore: true}) 128 | , jsFilter = $.filter('**/*.js', {restore: true}); 129 | 130 | return gulp.src($.mainBowerFiles(), {base: bowerDir}) 131 | .pipe(cssFilter) 132 | .pipe($.if(isProd, $.modifyCssUrls({ 133 | modify: function (url, filePath) { 134 | if (url.indexOf('http') !== 0 && url.indexOf('data:') !== 0) { 135 | filePath = path.dirname(filePath) + path.sep; 136 | filePath = filePath.substring(filePath.indexOf(bowerDir) + bowerDir.length, 137 | filePath.length); 138 | } 139 | url = path.normalize(filePath + url); 140 | url = url.replace(/[/\\]/g, '/'); 141 | return url; 142 | } 143 | }))) 144 | .pipe($.if(isProd, $.concat('vendor.css'))) 145 | .pipe($.if(isProd, $.cssmin())) 146 | .pipe($.if(isProd, $.rev())) 147 | .pipe(gulp.dest(config.extDir)) 148 | .pipe(cssFilter.restore) 149 | .pipe(jsFilter) 150 | .pipe($.if(isProd, $.concat('vendor.js'))) 151 | .pipe($.if(isProd, $.uglify({ 152 | preserveComments: $.uglifySaveLicense 153 | }))) 154 | .pipe($.if(isProd, $.rev())) 155 | .pipe(gulp.dest(config.extDir)) 156 | .pipe(jsFilter.restore); 157 | }); 158 | 159 | // inject bower components into index.html 160 | gulp.task('bowerInject', ['bowerCopy'], function () { 161 | if (isProd) { 162 | return gulp.src(config.buildDir + 'index.html') 163 | .pipe($.inject(gulp.src([ 164 | config.extDir + 'vendor*.css', 165 | config.extDir + 'vendor*.js' 166 | ], { 167 | read: false 168 | }), { 169 | starttag: '', 170 | endtag: '', 171 | addRootSlash: false, 172 | ignorePath: config.buildDir 173 | })) 174 | .pipe($.htmlmin({ 175 | collapseWhitespace: true, 176 | removeComments: true 177 | })) 178 | .pipe(gulp.dest(config.buildDir)); 179 | } else { 180 | return gulp.src(config.buildDir + 'index.html') 181 | .pipe($.wiredep.stream({ 182 | exclude: [/bootstrap[.]js/], 183 | ignorePath: '../../' + bowerDir.replace(/\\/g, '/'), 184 | fileTypes: { 185 | html: { 186 | replace: { 187 | css: function (filePath) { 188 | return ''; 190 | }, 191 | js: function (filePath) { 192 | return ''; 194 | } 195 | } 196 | } 197 | } 198 | })) 199 | .pipe(gulp.dest(config.buildDir)); 200 | } 201 | }); 202 | 203 | // copy Bower fonts and images into build directory 204 | gulp.task('bowerAssets', ['clean'], function () { 205 | var assetFilter = $.filter('**/*.{eot,otf,svg,ttf,woff,woff2,gif,jpg,jpeg,png}', {restore: true}); 206 | return gulp.src($.mainBowerFiles(), {base: bowerDir}) 207 | .pipe(assetFilter) 208 | .pipe(gulp.dest(config.extDir)) 209 | .pipe(assetFilter.restore); 210 | }); 211 | 212 | // copy custom fonts into build directory 213 | gulp.task('fonts', ['clean'], function () { 214 | var fontFilter = $.filter('**/*.{eot,otf,svg,ttf,woff,woff2}', {restore: true}); 215 | return gulp.src([config.appFontFiles]) 216 | .pipe(fontFilter) 217 | .pipe(gulp.dest(config.buildFonts)) 218 | .pipe(fontFilter.restore); 219 | }); 220 | 221 | // copy optional favicon in app directory 222 | gulp.task('favicon', ['clean'], function () { 223 | return gulp.src(path.join(config.appDir, 'favicon.ico')) 224 | .pipe(gulp.dest(config.buildDir)); 225 | }); 226 | 227 | // copy and optimize images into build directory 228 | gulp.task('images', ['clean'], function () { 229 | return gulp.src(config.appImageFiles) 230 | .pipe($.if(isProd, $.imagemin())) 231 | .pipe(gulp.dest(config.buildImages)); 232 | }); 233 | 234 | gulp.task('copyTemplates', ['bowerInject'], function () { 235 | // always copy templates to testBuild directory 236 | var stream = $.streamqueue({objectMode: true}); 237 | 238 | stream.queue(gulp.src([config.buildDirectiveTemplateFiles])); 239 | 240 | return stream.done() 241 | .pipe(gulp.dest(config.buildTestDirectiveTemplatesDir)); 242 | }); 243 | 244 | gulp.task('deleteTemplates', ['copyTemplates'], function (cb) { 245 | // only delete templates in production 246 | // the templates are injected into the app during prod build 247 | if (!isProd) { 248 | return cb(); 249 | } 250 | 251 | gulp.src([config.buildDir + '**/*.html']) 252 | .pipe(gulp.dest('tmp/' + config.buildDir)) 253 | .on('end', function () { 254 | $.del([ 255 | config.buildDir + '*', 256 | '!' + config.buildCss, 257 | '!' + config.buildFonts, 258 | '!' + config.buildImages, 259 | '!' + config.buildJs, 260 | '!' + config.extDir, 261 | '!' + config.buildDir + 'index.html' 262 | ], {mark: true}) 263 | .then(function () { 264 | cb(); 265 | }); 266 | }); 267 | }); 268 | 269 | gulp.task('build', ['deleteTemplates', 'bowerAssets', 'images', 'favicon', 'fonts']); 270 | }; 271 | -------------------------------------------------------------------------------- /app/builder/controllers/builder-controller.es6: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | class BuilderCtrl { 5 | constructor(Converter, growl, locker, $scope, $uibModal) { 6 | let vm = this; 7 | 8 | vm.forms = locker.get('schema_forms', { 9 | 'sample_form': { 10 | name: 'Sample Form', 11 | type: 'schema-form', 12 | fields: [ 13 | { 14 | type: 'text', 15 | key: 'first_name', 16 | title: 'First name', 17 | open: false 18 | }, 19 | { 20 | type: 'text', 21 | key: 'last_name', 22 | title: 'Last name', 23 | open: false 24 | }, 25 | { 26 | type: 'email', 27 | key: 'email', 28 | title: 'Email', 29 | open: false, 30 | showAdvance: true, 31 | fieldAddonRight:'' 32 | }, 33 | { 34 | type: 'date', 35 | key: 'dob', 36 | title: 'Date of Birth', 37 | open: false 38 | }, 39 | { 40 | type: 'dropdown', 41 | key: 'marital-status', 42 | title: 'Marital Status', 43 | open: false 44 | }, 45 | { 46 | type: 'date-time', 47 | key: 'check-in', 48 | title: 'Check In', 49 | open: false 50 | }, 51 | { 52 | type: 'date-time', 53 | key: 'check-out', 54 | title: 'Check Out', 55 | open: false 56 | }, 57 | { 58 | type: 'textarea', 59 | key: 'bio', 60 | title: 'Biography', 61 | open: false 62 | } 63 | ] 64 | } 65 | }); 66 | 67 | vm.schema = { 68 | type: 'object', 69 | title: 'Comment', 70 | properties: { 71 | name: { 72 | type: 'string' 73 | }, 74 | fields: { 75 | type: 'array', 76 | title: 'Fields', 77 | items: { 78 | type: 'object', 79 | properties: { 80 | open: { 81 | type: 'boolean', 82 | default: true 83 | }, 84 | type: { 85 | title: 'Type', 86 | type: 'string', 87 | enum: [ 88 | 'text', 89 | 'textarea', 90 | 'number', 91 | 'email', 92 | 'password', 93 | 'dropdown', 94 | 'radios', 95 | 'radios-inline', 96 | 'radiobuttons', 97 | 'checkbox', 98 | 'checkboxes', 99 | 'boolean', 100 | 'date', 101 | 'time', 102 | 'date-time', 103 | 'button', 104 | 'submit', 105 | 'reset', 106 | 'help', 107 | 'template' 108 | ] 109 | }, 110 | key: { 111 | title: 'Key', 112 | type: 'string', 113 | description: 'Unique identifier' 114 | }, 115 | title: { 116 | condition: 'model.notitle', 117 | title: 'Title', 118 | type: 'string' 119 | }, 120 | notitle: { 121 | type: 'boolean', 122 | title: 'Don\'t show title' 123 | }, 124 | description: { 125 | title: 'Description', 126 | type: 'string' 127 | }, 128 | validationMessage: { 129 | title: 'Validation Message', 130 | description: 'A custom validation error message. It can be a string, an object with error codes as key and messages as values or a custom message function', 131 | type: 'string' 132 | }, 133 | onChange: { 134 | title: 'onChange', 135 | description: 'onChange event handler, expression or function. For expression, modelValue and form are both available. For a function, they will be passed as parameters in that order', 136 | type: 'string' 137 | }, 138 | feedback: { 139 | title: 'Feedback Icon', 140 | description: 'Inline feedback icons. To turn off just set feedback to false. If set to a string that string is evaluated by a ngClass in the decorators scope. If not set att all the default value is { "glyphicon": true, "glyphicon-ok": hasSuccess(), "glyphicon-remove": hasError() }', 141 | type: 'string' 142 | }, 143 | disableSuccessState: { 144 | type: 'boolean', 145 | title: 'Disable Success State', 146 | default: false 147 | }, 148 | disableErrorState: { 149 | type: 'boolean', 150 | title: 'Disable Error State', 151 | default: false 152 | }, 153 | placeholder: { 154 | title: 'Placeholder', 155 | description: 'Placeholder on inputs and textarea', 156 | type: 'string' 157 | }, 158 | ngModelOptions: { 159 | title: 'ng-Model Options', 160 | description: 'Passed along to ng-model-options', 161 | type: 'string' 162 | }, 163 | readonly: { 164 | type: 'boolean', 165 | title: 'Readonly', 166 | default: false 167 | }, 168 | htmlClass: { 169 | title: 'Class', 170 | description: 'CSS Class(es) to be added to the container div e.g. : \'street foobar\'', 171 | type: 'string' 172 | }, 173 | destroyStrategy: { 174 | title: 'Destroy Strategy', 175 | description: 'One of null, empty , remove, or retain. Changes model on $destroy event. default is remove.', 176 | type: 'string' 177 | }, 178 | copyValueTo: { 179 | title: 'Copy Value To', 180 | description: 'Copy values to these schema keys e.g [\'address.street\']. The receiving fields can be shown, but the intent for them is to be hidden.', 181 | type: 'string' 182 | }, 183 | fieldHtmlClass: { 184 | title: 'Field Class', 185 | description: 'CSS Class(es) to be added to field input (or similar)', 186 | type: 'string' 187 | }, 188 | labelHtmlClass: { 189 | title: 'Label Class', 190 | description: 'CSS Class(es) to be added to the label of the field (or similar)', 191 | type: 'string' 192 | }, 193 | condition: { 194 | title: 'Condition', 195 | description: 'Show or hide field depending on an angular expression e.g \'model.age < 18\'. The expression has access to model, modelValue, arrayIndex. The condition need not reference a model value it could be anything on scope.', 196 | type: 'string' 197 | }, 198 | fieldAddonLeft: { 199 | title: 'Field Addon - Left', 200 | description: 'Add html code to left of input field. For reference check bootstrap input groups.', 201 | type: 'string' 202 | }, 203 | fieldAddonRight: { 204 | title: 'Field Addon - Right', 205 | description: 'Add html code to right of input field. For reference check bootstrap input groups.', 206 | type: 'string' 207 | }, 208 | onClick: { 209 | title: 'onClick', 210 | description: 'Function to call when a button/submit is clicked', 211 | type: 'string' 212 | }, 213 | showAdvanced: { 214 | title: 'Show advance options', 215 | type: 'boolean' 216 | } 217 | }, 218 | required: [ 219 | 'key' 220 | ] 221 | } 222 | } 223 | }, 224 | required: ['name'] 225 | }; 226 | vm.model = {}; 227 | vm.form = [ 228 | { 229 | type: 'section', 230 | htmlClass: 'row main-info', 231 | items: [ 232 | { 233 | key: 'name', 234 | htmlClass: 'col-sm-6', 235 | placeholder: 'name of the form', 236 | notitle: true, 237 | fieldHtmlClass: 'field-name', 238 | labelHtmlClass: 'field-name-label' 239 | } 240 | ] 241 | }, 242 | { 243 | type: 'section', 244 | htmlClass: 'my-field row', 245 | items: [ 246 | { 247 | type: 'section', 248 | htmlClass: 'col-sm-6', 249 | items: [ 250 | { 251 | type: 'help', 252 | helpvalue: '

My Fields:

' 253 | } 254 | ] 255 | }, 256 | { 257 | type: 'section', 258 | htmlClass: 'col-sm-6', 259 | items: [ 260 | { 261 | type: 'button', 262 | onClick: function () { 263 | vm.instructionsVisible = true; 264 | }, 265 | style: 'btn-success btn-sm pull-right', 266 | title: 'Generate Schema' 267 | }, 268 | { 269 | type: 'submit', 270 | style: 'btn-success btn-sm pull-right margin-right-20', 271 | title: 'Save' 272 | } 273 | ] 274 | } 275 | ] 276 | }, 277 | { 278 | key: 'fields', 279 | type: 'accordion-array', 280 | title: '{{ value.title || "Field "+ $index}}', 281 | add: 'Add a new Field', 282 | remove: 'Remove Field', 283 | startEmpty: true, 284 | items: [ 285 | { 286 | key: 'fields[].title', 287 | htmlClass: 'hide-label', 288 | placeholder: 'Title' 289 | }, 290 | { 291 | type: 'section', 292 | htmlClass: 'row', 293 | items: [ 294 | { 295 | key: 'fields[].type', 296 | placeholder: 'Type', 297 | notitle: true, 298 | htmlClass: 'col-sm-6 hide-label', 299 | }, 300 | { 301 | key: 'fields[].key', 302 | //type: 'section', 303 | placeholder: 'Key (Unique Identifier)', 304 | notitle: true, 305 | htmlClass: 'col-sm-6 hide-label', 306 | } 307 | ] 308 | }, 309 | { 310 | key: 'fields[].open', 311 | notitle: true, 312 | type: 'hidden' 313 | }, 314 | { 315 | key: 'fields[].description', 316 | type: 'textarea', 317 | placeholder: 'Description', 318 | notitle: true, 319 | }, 320 | { 321 | type: 'section', 322 | htmlClass: 'row', 323 | items: [ 324 | { 325 | key: 'fields[].notitle', 326 | htmlClass: 'col-sm-6' 327 | }, 328 | { 329 | key: 'fields[].showAdvanced', 330 | htmlClass: 'col-sm-6' 331 | }, 332 | ] 333 | }, 334 | { 335 | condition: 'model.fields[arrayIndex].showAdvanced', 336 | type: 'help', 337 | helpvalue: '
' 338 | }, 339 | { 340 | type: 'section', 341 | htmlClass: 'row', 342 | items: [ 343 | { 344 | type: 'section', 345 | htmlClass: 'col-md-4', 346 | items: [ 347 | { 348 | condition: 'model.fields[arrayIndex].showAdvanced', 349 | key: 'fields[].disableSuccessState' 350 | } 351 | ] 352 | }, 353 | { 354 | type: 'section', 355 | htmlClass: 'col-md-4', 356 | items: [ 357 | { 358 | condition: 'model.fields[arrayIndex].showAdvanced', 359 | key: 'fields[].disableErrorState' 360 | } 361 | ] 362 | }, 363 | { 364 | type: 'section', 365 | htmlClass: 'col-md-4', 366 | items: [ 367 | { 368 | condition: 'model.fields[arrayIndex].showAdvanced', 369 | key: 'fields[].readonly' 370 | }, 371 | ] 372 | } 373 | ] 374 | }, 375 | { 376 | condition: 'model.fields[arrayIndex].showAdvanced', 377 | type: 'help', 378 | helpvalue: '
' 379 | }, 380 | { 381 | condition: 'model.fields[arrayIndex].showAdvanced', 382 | key: 'fields[].validationMessage', 383 | type: 'textarea' 384 | }, 385 | { 386 | condition: 'model.fields[arrayIndex].showAdvanced', 387 | key: 'fields[].onChange', 388 | type: 'textarea' 389 | }, 390 | { 391 | condition: 'model.fields[arrayIndex].showAdvanced', 392 | key: 'fields[].feedback' 393 | }, 394 | { 395 | condition: 'model.fields[arrayIndex].showAdvanced', 396 | key: 'fields[].placeholder' 397 | }, 398 | { 399 | condition: 'model.fields[arrayIndex].showAdvanced', 400 | key: 'fields[].ngModelOptions', 401 | type: 'textarea' 402 | }, 403 | { 404 | condition: 'model.fields[arrayIndex].showAdvanced', 405 | key: 'fields[].htmlClass' 406 | }, 407 | { 408 | condition: 'model.fields[arrayIndex].showAdvanced', 409 | key: 'fields[].destroyStrategy' 410 | }, 411 | { 412 | condition: 'model.fields[arrayIndex].showAdvanced', 413 | key: 'fields[].copyValueTo' 414 | }, 415 | { 416 | condition: 'model.fields[arrayIndex].showAdvanced', 417 | key: 'fields[].fieldHtmlClass' 418 | }, 419 | { 420 | condition: 'model.fields[arrayIndex].showAdvanced', 421 | key: 'fields[].labelHtmlClass' 422 | }, 423 | { 424 | condition: 'model.fields[arrayIndex].showAdvanced', 425 | key: 'fields[].condition' 426 | }, 427 | { 428 | condition: 'model.fields[arrayIndex].showAdvanced', 429 | 'key': 'fields[].fieldAddonLeft' 430 | }, 431 | { 432 | condition: 'model.fields[arrayIndex].showAdvanced', 433 | key: 'fields[].fieldAddonRight' 434 | } 435 | 436 | ] 437 | } 438 | ]; 439 | vm.output = {schema: {}, form: []}; 440 | 441 | vm.saveForm = (form) => { 442 | $scope.$broadcast('schemaFormValidate'); 443 | if (form.$valid) { 444 | persistForm(); 445 | } 446 | }; 447 | vm.newForm = () => { 448 | persistForm(); 449 | vm.model = { 450 | fields: [] 451 | }; 452 | console.log(vm.model); 453 | }; 454 | vm.openForm = () => { 455 | var modalInstance = $uibModal.open({ 456 | templateUrl: 'builder/views/open.tpl.html', 457 | controller: function ($uibModalInstance, forms) { 458 | let vm = this; 459 | vm.forms = forms; 460 | vm.open = function (form) { 461 | $uibModalInstance.close(form); 462 | }; 463 | 464 | vm.delete = function (index, form) { 465 | if (confirm('About to delete ' + form.name)) { 466 | vm.forms.slice(vm.forms, index, 1); 467 | locker.put('schema_forms', vm.forms); 468 | } 469 | }; 470 | 471 | vm.cancel = function () { 472 | $uibModalInstance.dismiss(); 473 | } 474 | }, 475 | controllerAs: 'modal', 476 | resolve: { 477 | forms: function () { 478 | return vm.forms; 479 | } 480 | } 481 | }); 482 | 483 | modalInstance.result.then(function (form) { 484 | persistForm(); 485 | vm.model = form; 486 | generateOutput(vm.model); 487 | }); 488 | }; 489 | 490 | if (!vm.model.name) { 491 | vm.openForm(); 492 | } 493 | 494 | function generateOutput(update) { 495 | vm.output = Converter.generateFields(update); 496 | console.log(vm.output); 497 | vm.display = angular.copy(vm.output); 498 | } 499 | 500 | function persistForm() { 501 | if (vm.model.name && vm.model.name.length > 0) { 502 | vm.forms[_.snakeCase(vm.model.name)] = vm.model; 503 | locker.put('schema_forms', vm.forms); 504 | growl.success('Form ' + vm.model.name + ' Saved'); 505 | } 506 | } 507 | 508 | 509 | $scope.$watch(() => vm.model, function (update) { 510 | generateOutput(update); 511 | }, true); 512 | } 513 | 514 | } 515 | /** 516 | * @ngdoc object 517 | * @name builder.controller:BuilderCtrl 518 | * 519 | * @description 520 | * 521 | */ 522 | angular.module('builder').controller('BuilderCtrl', BuilderCtrl); 523 | }() 524 | ); 525 | --------------------------------------------------------------------------------