|
1 | 1 | Extending Schema Form
|
2 | 2 | =====================
|
3 | 3 |
|
4 |
| -Extending schema form with new form field via add-ons or changing to another CSS framework (a new |
5 |
| -decorator) is easy. But how you do it has changed recently due to "the new builder". |
| 4 | +Schema Form is designed to be easily extended. You can add your own custom fields or completely |
| 5 | +change the how the entire form is rendered. |
6 | 6 |
|
7 |
| -New documentation is in the works and will come soon. |
| 7 | +A custom field is called an **add-on** and you can find community add-ons listed over at |
| 8 | +[schemaform.io](http://schemaform.io). |
| 9 | + |
| 10 | +To completely change how the entire field is rendered you need to create what we call a **decorator**. |
| 11 | +A decorator is actually a collection of add-ons that at least cover the basic field types |
| 12 | +that a schema can default to, but usually a lot more. |
| 13 | + |
| 14 | +But before we get into the details of how you define a decorator or an add-on, let's take a look at how schema form builds forms. |
| 15 | + |
| 16 | +How the form is built |
| 17 | +---------------------- |
| 18 | +Schema Form uses the [sfBuilder](https://github.com/Textalk/angular-schema-form/blob/development/src/services/builder.js) |
| 19 | +service to recursively build the DOM elements of the form from a *canonical form definition*, that |
| 20 | +is our fancy word for an internal representation of a merge between the schema and the form. |
| 21 | + |
| 22 | +It's always an array of object and each object at least have the property `type`. If a `type` was |
| 23 | +not set in the form definition given to `sf-form` the schema is used to get a default. |
| 24 | + |
| 25 | +Example canonical form def. |
| 26 | +```js |
| 27 | +[ |
| 28 | + { |
| 29 | + type: 'text' |
| 30 | + key: 'name' |
| 31 | + }, |
| 32 | + { |
| 33 | + type: 'fieldset', |
| 34 | + item: [ |
| 35 | + { |
| 36 | + type: 'textarea', |
| 37 | + key: 'comment' |
| 38 | + } |
| 39 | + ] |
| 40 | + } |
| 41 | +] |
| 42 | +``` |
| 43 | + |
| 44 | +#### The actual building |
| 45 | +So to build a form from a canonical form definition as in the example above the builder service loops |
| 46 | +over and for each type asks the decorator for a template, it adds it to a document fragment. |
| 47 | + |
| 48 | +After adding the template it also asks the decorator if that type has a *builder* |
| 49 | +function (actually it's usually a list of functions). If so it calls it with the DOM of its template, |
| 50 | +the form definition for that field and other useful stuff. This way the builder can modify and |
| 51 | +prepare the template depending on options in that fields form object. |
| 52 | + |
| 53 | +Nested fields, as with the fieldset above in the example above, builds it's children with such a |
| 54 | +*builder* function. |
| 55 | + |
| 56 | +This all happens in one large swoop and the finished document fragment is popped inside the form |
| 57 | +and then `$compile` is used to kick start it's directives. |
| 58 | + |
| 59 | + |
| 60 | +Creating an add-on |
| 61 | +------------------ |
| 62 | + |
| 63 | +So to create an add-on you need two things, a template and some *builder functions*. Fortunately |
| 64 | +schema form got you covered with a couple of standard builders so most of the time you will only |
| 65 | +need a template. |
| 66 | + |
| 67 | +To register your template to be used when the form definition has a specific type you use the |
| 68 | +`schemaFormDecoratorsProvider.defineAddOn`. |
| 69 | + |
| 70 | +Ex. |
| 71 | +```js |
| 72 | +angular.module('myAddOnModule', ['schemaForm']).config(function(schemaFormDecoratorsProvider, sfBuilderProvider) { |
| 73 | + |
| 74 | + schemaFormProvider.defineAddOn( |
| 75 | + 'bootstrapDecorator', // Name of the decorator you want to add to. |
| 76 | + 'awesome', // Form type that should render this add-on |
| 77 | + 'templates/my/addon.html', // Template name in $templateCache |
| 78 | + sfBuilderProvider.stdBuilders // List of builder functions to apply. |
| 79 | + ); |
| 80 | + |
| 81 | +}); |
| 82 | +``` |
| 83 | + |
| 84 | +The standards builders are `[sfField, ngModel, ngModelOptions, condition]`, see usage details below. |
| 85 | + |
| 86 | +#### The Template |
| 87 | +So whats in a template? You usually need a couple of things: |
| 88 | + |
| 89 | + 1. Usually a top level element that surrounds your template is a good idea. The `sfField` builder |
| 90 | + slaps on a `sfField` directive that exposes the current form object on scope as `form`. |
| 91 | + 1. A `sf-field-model` somewhere so that the `ngModel` builder can add a proper `ngModel` to bind |
| 92 | + your model value to. |
| 93 | + 1. A `schema-validate="form"` directive on the same element to enable schema validation. |
| 94 | + 1. A `<div sf-message="form.description"></div>` to display description or error messages. |
| 95 | + |
| 96 | +Basic template example: |
| 97 | +```html |
| 98 | +<div> <!-- Surrounding DIV for sfField builder to add a sfField directive to. --> |
| 99 | + <label>{{form.title}}</div> |
| 100 | + <input sf-field-model schema-validate="form" type="text"> |
| 101 | + <div sf-message="form.description"></div> |
| 102 | +</div> |
| 103 | +``` |
| 104 | + |
| 105 | +**BIG FAT CAVEAT** |
| 106 | +Ok, so currently there is something really ugly here. The bootstrap (and material) decorator uses |
| 107 | +a build step (gulp-angular-templatecache) to "compile" the template into a javascript file that |
| 108 | +basically chucks the template into `$templateCache`. Currently schema form does *not* support |
| 109 | +loading the templates any other way. They need to be in `$templateCache` when rendering. |
| 110 | + |
| 111 | +This is really ugly and will be fixed. But you have been warned! |
| 112 | + |
| 113 | + |
| 114 | +Defining a decorator |
| 115 | +-------------------- |
| 116 | +Defining a decorator is basically the same as defining a lot of add-ons. As with add-ons you use |
| 117 | +the `schemaFormDecoratorsProvider` again. This time its |
| 118 | +`schemaFormDecoratorsProvider.defineDecorator`. |
| 119 | + |
| 120 | +Ex. |
| 121 | +```js |
| 122 | +angular.module('myDecoratorModule', ['schemaForm']).config(function(schemaFormDecoratorsProvider, sfBuilderProvider) { |
| 123 | + |
| 124 | + schemaFormDecoratorsProvider.defineDecorator('awesomeDecorator', { |
| 125 | + textarea: {template: base + 'textarea.html', builder: sfBuilderProvider.stdBuilders}, |
| 126 | + button: {template: base + 'submit.html', builder: sfBuilderProvider.stdBuilders}, |
| 127 | + text: {template: base + 'text.html', builder: sfBuilderProvider.stdBuilders}, |
| 128 | + |
| 129 | + // The default is special, if the builder can't find a match it uses the default template. |
| 130 | + 'default': {template: base + 'default.html', builder: sfBuilderProvider.stdBuilders} |
| 131 | + }, []); |
| 132 | +}); |
| 133 | +``` |
| 134 | + |
| 135 | +### Setting up schema defaults |
| 136 | +So you got this shiny new add-on or decorator that adds a fancy field type, but feel a bit bummed out that you |
| 137 | +need to specify it in the form definition all the time? Fear not because you can also add a "rule" |
| 138 | +to map certain types and conditions in the schema to default to your type. |
| 139 | + |
| 140 | +You do this by adding to the `schemaFormProvider.defaults` object. The `schemaFormProvider.defaults` |
| 141 | +is an object with a key for each type *in JSON Schema* with a array of functions as its value. |
| 142 | + |
| 143 | +```javscript |
| 144 | +var defaults = { |
| 145 | + string: [], |
| 146 | + object: [], |
| 147 | + number: [], |
| 148 | + integer: [], |
| 149 | + boolean: [], |
| 150 | + array: [] |
| 151 | +}; |
| 152 | +``` |
| 153 | + |
| 154 | +When schema form traverses the JSON Schema to create default form definitions it first checks the |
| 155 | +*JSON Schema type* and then calls on each function in the corresponding list *in order* until a |
| 156 | +function actually returns something. That is then used as a defualt. |
| 157 | + |
| 158 | +This is the function that makes it a datepicker if its a string and has format "date" or "date-time": |
| 159 | + |
| 160 | +```javascript |
| 161 | +var datepicker = function(name, schema, options) { |
| 162 | + if (schema.type === 'string' && (schema.format === 'date' || schema.format === 'date-time')) { |
| 163 | + var f = schemaFormProvider.stdFormObj(name, schema, options); |
| 164 | + f.key = options.path; |
| 165 | + f.type = 'datepicker'; |
| 166 | + options.lookup[sfPathProvider.stringify(options.path)] = f; |
| 167 | + return f; |
| 168 | + } |
| 169 | +}; |
| 170 | + |
| 171 | +// Put it first in the list of functions |
| 172 | +schemaFormProvider.defaults.string.unshift(datepicker); |
| 173 | +``` |
| 174 | + |
| 175 | +### Sharing your add-on with the world |
| 176 | +So you made an add-on, why not share it with us? On the front page, |
| 177 | +[http://textalk.github.io/angular-schema-form/](http://textalk.github.io/angular-schema-form/#third-party-addons), we |
| 178 | +maintain a list of add ons based on a query of the bower register, and we love to see your add-on |
| 179 | +there. |
| 180 | + |
| 181 | +Any [bower](http://bower.io) package with a name starting with `angular-schema-form-` or that has |
| 182 | +the `keyword` `angular-schema-form-add-on` in its `bower.json` will be picked up. It's cached so |
| 183 | +there can be a delay of a day or so. |
| 184 | + |
| 185 | +So [make a bower package](http://bower.io/docs/creating-packages/), add the keyword |
| 186 | +`angular-schema-form-add-on` and [register it](http://bower.io/docs/creating-packages/#register)! |
| 187 | + |
| 188 | + |
| 189 | + |
| 190 | + |
| 191 | +The builders |
| 192 | +------------ |
| 193 | +TODO: API docs for each builder |
| 194 | + |
| 195 | + |
| 196 | +Useful directives |
| 197 | +----------------- |
| 198 | +TODO: more in depth about schema-validate and sf-messages |
0 commit comments