├── .gitignore ├── .jshintrc ├── LICENSE ├── README.md ├── bower.json ├── demo.js ├── index.html ├── index.js ├── template.html └── wireframe.svg /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "undef": true, 3 | "unused": true, 4 | "browser": true, 5 | "globals": { 6 | "console":false, 7 | "jQuery": false, 8 | "$":false, 9 | "assertEquals": false, 10 | "jstestdriver": false, 11 | "assertTrue": false, 12 | "assertFalse": false, 13 | "describe": false, 14 | "it":false, 15 | "expect": false, 16 | "sinon":false, 17 | "beforeEach": false, 18 | "afterEach": false, 19 | "angular": false, 20 | "module": false, 21 | "inject": false, 22 | "chai": false, 23 | "should": false, 24 | "Jed": false, 25 | "tws": false 26 | } 27 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Mike Marcacci 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-schema-form-builder 2 | A user interface for building definitions for angular-schema-form. 3 | 4 | This repo is a response to [Schema-Form Issue #304](https://github.com/Textalk/angular-schema-form/issues/304). At the moment it's really just a public design document to facilitat collaboration from the community of Schema-Form users. 5 | 6 | Main Goals 7 | ---------- 8 | - graphical interface for building form definitions, given a pre-existing schema 9 | - direct integration with schema-form wherever possible (don't duplicate traversal logic/etc) 10 | - direct integration with registered custom form types 11 | 12 | Secondary Goals 13 | --------------- 14 | - graphical interface for building a form definition, and its corresponding schema 15 | 16 | 17 | Strategy 18 | -------- 19 | 20 | ![Palette, Designer, Inspector](https://rawgithub.com/mike-marcacci/angular-schema-form-builder/master/wireframe.svg) 21 | 22 | ###Palette 23 | - a list of possible form types, populated from the configured decorator 24 | - drag a list item to the preview section to insert a new form item 25 | 26 | ###Designer 27 | - form is rendered by schema-form 28 | - uses a dummy model so the user can test validations 29 | - drag field to reorder 30 | - click field to inspect 31 | 32 | ###Inspector 33 | - shows configuration options for the form item selected in the preview section 34 | - configuration options are rendered by schema-form 35 | - configuration options are populated by the form type's definition on the decorator 36 | - changes are reflected in the preview section 37 | - a custom `builder-schema-key` form type is available for selecting the `key` property 38 | 39 | 40 | 41 | Changes to Schema-Form 42 | ---------------------- 43 | 44 | For the above to be possible, schema-form will have to be modified to allow an object to be used when registering a form type with a decorator: 45 | 46 | ```js 47 | 48 | // old style (should maintain backwards-compatibility) 49 | schemaFormDecoratorsProvider.addMapping( 50 | 'bootstrapDecorator', 51 | 'datepicker', 52 | 'directives/decorators/bootstrap/datepicker/datepicker.html' 53 | ); 54 | 55 | // new style defining title, description, schema, and form for configuration 56 | schemaFormDecoratorsProvider.addMapping( 57 | 'bootstrapDecorator', 58 | { 59 | type: 'datepicker', 60 | title: 'DatePicker', 61 | description: 'Datepicker add-on for Angular Schema Form using pickadate!', 62 | form: ['*'], 63 | schema: { 64 | type: 'object', 65 | properties: { 66 | minDate: { 67 | type: ['string', 'null'] 68 | }, 69 | maxDate: { 70 | type: ['string', 'null'] 71 | } 72 | } 73 | } 74 | }, 75 | 'directives/decorators/bootstrap/datepicker/datepicker.html' 76 | ); 77 | ``` -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-schema-form-builder", 3 | "version": "0.0.0", 4 | "homepage": "https://github.com/mike-marcacci/angular-schema-form-builder", 5 | "authors": [ 6 | "Mike Marcacci " 7 | ], 8 | "license": "MIT", 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "test", 14 | "tests" 15 | ], 16 | "dependencies": { 17 | "angular-schema-form": "~0.8.0", 18 | "angular-ui-sortable": "~0.13.3" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /demo.js: -------------------------------------------------------------------------------- 1 | angular.module('demo', ['schemaFormBuilder']) 2 | 3 | .controller('demo', function($scope){ 4 | $scope.schema = { 5 | "type": "object", 6 | "properties": { 7 | "name": { "type": "string" }, 8 | "email": { "type": "string" }, 9 | "password": { "type": "string" } 10 | } 11 | }; 12 | $scope.form = [ 13 | 'name', 14 | 'email', 15 | { 16 | key: 'password', 17 | type: 'password', 18 | description: 'This doesn\'t appear to work...' 19 | } 20 | ]; 21 | }) -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 28 | 29 |
30 |

This is to demonstrate that the presense of the builder doesn't influence any other schema-forms (despite modifying the decorators).

31 |
36 |
37 | 38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('schemaFormBuilder', ['schemaForm']) 4 | 5 | 6 | // extend the decorator with builder controls 7 | .config(function($provide){ 8 | 9 | // TODO: we need to extend ALL registered decorators somehow... 10 | $provide.decorator('bootstrapDecoratorDirective', function ($delegate, $compile) { 11 | var directive = $delegate[0]; 12 | var link = directive.link; 13 | 14 | directive.compile = function(){ 15 | return function(scope, element, attrs){ 16 | 17 | // we've already injected the builder controls directive 18 | if(typeof attrs.sfBuilderControls !== 'undefined') 19 | return link.apply(this, arguments); 20 | 21 | // inject the builder controls directive 22 | element.attr('sf-builder-controls', ''); 23 | $compile(element)(scope); 24 | }; 25 | }; 26 | 27 | return $delegate; 28 | }); 29 | }) 30 | 31 | .directive('sfBuilder', [function(){ 32 | return { 33 | templateUrl: 'template.html', 34 | restrict: 'EA', 35 | transclude: false, 36 | scope: { 37 | options: '=sfBuilderOptions', 38 | schema: '=sfBuilderSchema', 39 | form: '=sfBuilderForm' 40 | }, 41 | link: function link(scope, element, attrs, controller, transclude) { 42 | 43 | }, 44 | controller: ['$scope', function($scope){ 45 | 46 | // inspect a form item 47 | this.inspectItem = function(item) { 48 | $scope.$apply(function(){ 49 | $scope.inspected = item; 50 | }); 51 | }; 52 | 53 | 54 | 55 | 56 | 57 | $scope.model = {}; 58 | $scope.types = { 59 | text: { 60 | title: 'Text', 61 | schema: { 62 | type: 'object', 63 | properties: { 64 | title: { 'type': 'string' }, 65 | description: { 'type': 'string' } 66 | } 67 | }, 68 | form: [ 69 | 'title', 70 | 'description' 71 | ] 72 | }, 73 | password: { 74 | title: 'Password', 75 | schema: { 76 | type: 'object', 77 | properties: { 78 | title: { 'type': 'string' }, 79 | description: { 'type': 'string' } 80 | } 81 | }, 82 | form: [ 83 | 'title', 84 | 'description' 85 | ] 86 | } 87 | } 88 | 89 | 90 | }] 91 | } 92 | }]) 93 | 94 | 95 | .directive('sfBuilderDesigner', [function(){ 96 | return { 97 | restrict: 'EA', 98 | require: ['^^sfBuilder'], 99 | scope: false, 100 | controller: ['$scope', function($scope){}] 101 | } 102 | }]) 103 | 104 | 105 | .directive('sfBuilderPallette', [function(){ 106 | return { 107 | restrict: 'EA', 108 | require: ['^^sfBuilder'], 109 | scope: false, 110 | controller: ['$scope', function($scope){}] 111 | } 112 | }]) 113 | 114 | 115 | .directive('sfBuilderInspector', [function(){ 116 | return { 117 | restrict: 'EA', 118 | require: ['^^sfBuilder'], 119 | scope: false, 120 | controller: ['$scope', function($scope){}] 121 | } 122 | }]) 123 | 124 | 125 | .directive('sfBuilderControls', [function(){ 126 | return { 127 | restrict: 'A', 128 | require: ['?^^sfBuilder', '?^^sfBuilderDesigner'], 129 | priority: -1000, 130 | scope: false, 131 | link: function link(scope, element, attrs, controllers) { 132 | var sfBuilder = controllers[0]; 133 | var sfBuilderDesigner = controllers[1]; 134 | 135 | 136 | // only apply this if we're inside the designer 137 | if(!sfBuilder || !sfBuilderDesigner) return; 138 | 139 | // make draggable 140 | element.attr('draggable', 'true'); 141 | 142 | // the parent 143 | element.addClass('sf-builder-decorator'); 144 | element.on('click', function(event){ 145 | sfBuilder.inspectItem(scope.form) 146 | }); 147 | 148 | 149 | // the controls DOM 150 | var controls = angular.element('
'); 151 | controls.append('
'); 152 | 153 | 154 | // add the controls DOM 155 | element.append(controls); 156 | 157 | // if the controls DOM got removed, add it back 158 | new MutationObserver(function(mutations) { 159 | mutations.forEach(function(mutation) { 160 | for (var i = mutation.removedNodes.length - 1; i >= 0; i--) { 161 | if(mutation.removedNodes[i] != controls[0]) continue; 162 | element.append(controls); 163 | }; 164 | }); 165 | }).observe(element[0], { 166 | attributes: false, 167 | childList: true, 168 | characterData: false 169 | }); 170 | 171 | }, 172 | controller: ['$scope', function($scope){ 173 | 174 | }] 175 | } 176 | }]) 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /template.html: -------------------------------------------------------------------------------- 1 | 31 | 32 | 33 | 34 |
35 |
36 |
Types
37 |
38 |
39 |
    40 |
  • {{ definition.title || type }}
  • 41 |
42 |
43 |
44 |
45 | 46 | 47 |
52 |
53 | 54 | 55 |
56 |
57 |
Inspector
58 |
59 |
Form type not supported.
60 |
61 |
66 |
67 |
68 |
69 | 70 |
71 | 72 | -------------------------------------------------------------------------------- /wireframe.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | Palette 10 | Designer 11 | Inspector 12 | 13 | --------------------------------------------------------------------------------