Skip to content

Commit 5da940c

Browse files
committed
Docs on extending Schema Form
1 parent 1f489b7 commit 5da940c

File tree

3 files changed

+204
-1
lines changed

3 files changed

+204
-1
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ can find them here with usage instructions:
142142
* [https://github.com/Textalk/angular-schema-form-datepicker](https://github.com/Textalk/angular-schema-form-datepicker)
143143
* [https://github.com/Textalk/angular-schema-form-colorpicker](https://github.com/Textalk/angular-schema-form-colorpicker)
144144

145+
Your can also [create your own add-ons.](docs/extending.md)
146+
145147
Building
146148
--------
147149
The files in the `dist/` folder, plus dependencies, are all you need to use Schema Form. But if you'd like to build it yourself, we use [gulp](http://gulpjs.com/).
@@ -180,7 +182,10 @@ $ karma start karma.conf.js
180182
Contributing
181183
------------
182184

183-
All contributions are welcome! We're trying to use
185+
All contributions are welcome! If its a new field type consider making it an add-on instead,
186+
especially if it has dependecies. See [extending Schema Form documentation.](docs/extending.md)
187+
188+
We're trying to use
184189
[git flow](http://danielkummer.github.io/git-flow-cheatsheet/), so please base any merge request
185190
on the **development** branch instead of **master**.
186191

docs/extending.md

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
Extending Schema Form
2+
=====================
3+
Schema Form is designed to be easily extended and there are two basic ways to do it:
4+
5+
1. Add a new type of field
6+
2. Add a new decorator
7+
8+
9+
Adding a Field
10+
--------------
11+
To add a new field to Schema Form you need to create a new form type and match that form type with
12+
a template snippet. To do this you use the `schemaFormDecoratorsProvider.addMapping()` function.
13+
14+
Ex. from the [datepicker add-on](https://github.com/Textalk/angular-schema-form-datepicker/blob/master/src/bootstrap-datepicker.js#L18)
15+
```javascript
16+
schemaFormDecoratorsProvider.addMapping(
17+
'bootstrapDecorator',
18+
'datepicker',
19+
'directives/decorators/bootstrap/datepicker/datepicker.html'
20+
);
21+
```
22+
23+
The second argument is the name of your new form type, in this case `datepicker`, and the third is
24+
the template we bind to it (the first is the decorator, use `bootstrapDecorator` unless you know
25+
what you are doing).
26+
27+
What this means is that a form definition like this:
28+
```javascript
29+
$scope.form = [
30+
{
31+
key: "birthday",
32+
type: "datepicker"
33+
}
34+
];
35+
```
36+
...will result in the `datepicker.html` template to be used to render that field in the form.
37+
38+
But wait, where is all the code? Basically it's then up to the template to use directives to
39+
implement whatever it likes to do. It does have some help though, lets look at template example and
40+
go through the basics.
41+
42+
This is the template for the datepicker:
43+
```html
44+
<div class="form-group" ng-class="{'has-error': hasError()}">
45+
<label class="control-label" ng-show="showTitle()">{{form.title}}</label>
46+
47+
<input ng-show="form.key"
48+
style="background-color: white"
49+
type="text"
50+
class="form-control"
51+
schema-validate="form"
52+
ng-model="$$value$$"
53+
pick-a-date
54+
min-date="form.minDate"
55+
max-date="form.maxDate"
56+
format="form.format" />
57+
58+
<span class="help-block">{{ (hasError() && errorMessage(schemaError())) || form.description}}</span>
59+
</div>
60+
```
61+
62+
### What's on the scope?
63+
Each form field will be rendered inside a decorator directive, created by the
64+
`schemaFormDecorators` factory service, *do*
65+
[check the source](https://github.com/Textalk/angular-schema-form/blob/master/src/services/decorators.js#L33).
66+
67+
This means you have several helper functions and values on scope, most important of this `form`. The
68+
`form` variable contains the merged form definition for that field, i.e. your supplied form object +
69+
the defaults from the schema (it also has its part of the schema under *form.schema*).
70+
This is how you define and use new form field options, whatever is set on the form object is
71+
available here for you to act on.
72+
73+
| Name | What it does |
74+
|----------|----------------|
75+
| form | Form definition object |
76+
| showTitle() | Shorthand for `form && form.notitle !== true && form.title` |
77+
| errorMessage(msg) | Error message formatting, makes validationMessage option work. |
78+
| evalInScope(expr, locals) | Eval supplied expression, ie scope.$eval |
79+
| evalExpr(expr, locals) | Eval an expression in the parent scope of the main `sf-schema` directive. |
80+
| buttonClick($event, form) | Use this with ng-click to execute form.onClick |
81+
82+
### The magic $$value$$
83+
Schema Form wants to play nice with the built in Angular directives for form. Especially `ng-model`
84+
which we want to handle the two way binding against our model value. Also by using `ng-model` we
85+
get all the nice validation states from the `ngModelController` and `FormController` that we all
86+
know and love.
87+
88+
To get that working properly we had to resort to a bit of trickery, right before we let Angular
89+
compile the field template we do a simple string replacement of `$$value$$` and replace that
90+
with the path to the current form field on the model, i.e. `form.key`.
91+
92+
So `ng-model="$$value$$"` becomes something like `ng-model="model['person']['address']['street']"`,
93+
you can see this if you inspect the final form in the browser.
94+
95+
So basically always have a `ng-model="$$value$$"` (Pro tip: ng-model is fine on any element, put
96+
it on the same div as your custom directive and require the ngModelController for full control).
97+
98+
### schema-validate directive
99+
`schema-validate` is a directive that you should put on the same element as your `ng-model`. It is
100+
responsible for validating the value against the schema using [tv4js](https://github.com/geraintluff/tv4)
101+
It takes the form definition as an argument.
102+
103+
`schema-validate` also exports some things on the scope:
104+
| Name | What it does |
105+
|----------|--------------|
106+
| ngModel | the ngModelController |
107+
| hasSuccess() | Shorthand for `ngModel.$valid && (!ngModel.$pristine || !ngModel.$isEmpty(ngModel.$modelValue))` |
108+
| hasError() | Shorthand for `ngModel.$invalid && !ngModel.$pristine` |
109+
| schemaError() | The current error object from tv4js |
110+
111+
112+
### Setting up schema defualts
113+
So you got this shiny new add-on that adds a fancy field type, but feel a bit bummed out that you
114+
need to specify it in the form definition all the time? Fear not because you can also add a "rule"
115+
to map certain types and conditions in the schema to default to your type.
116+
117+
You do this by adding to the `schemaFormProvider.defaults` object. The `schemaFormProvider.defaults`
118+
is an object with a key for each type *in JSON Schema* with a array of functions as its value.
119+
120+
```javscript
121+
var defaults = {
122+
string: [],
123+
object: [],
124+
number: [],
125+
integer: [],
126+
boolean: [],
127+
array: []
128+
};
129+
```
130+
131+
When schema form traverses the JSON Schema to create default form definitions it first checks the
132+
*JSON Schema type* and then calls on each function in the corresponding list *in order* until a
133+
function actually returns something. That is then used as a defualt.
134+
135+
This is the function that makes it a datepicker if its a string and has format "date" or "date-time":
136+
137+
```javascript
138+
var datepicker = function(name, schema, options) {
139+
if (schema.type === 'string' && (schema.format === 'date' || schema.format === 'date-time')) {
140+
var f = schemaFormProvider.stdFormObj(name, schema, options);
141+
f.key = options.path;
142+
f.type = 'datepicker';
143+
options.lookup[sfPathProvider.stringify(options.path)] = f;
144+
return f;
145+
}
146+
};
147+
148+
// Put it first in the list of functions
149+
schemaFormProvider.defaults.string.unshift(datepicker);
150+
```
151+
152+
Decorators
153+
----------
154+
Decorators are a second way to extend Schema Form, the thought being that you should easily be able
155+
to change *every* field. Maybe you like it old school and want to use bootstrap 2. Or maybe you like
156+
to generate a table with the data instead? Right now there are no other decorators than bootstrap 3.
157+
158+
Basically a *decorator* sets up all the mappings between form types and their respective templates
159+
using the `decoratorsProvider.createDecorator()` function.
160+
161+
```javascript
162+
var base = 'directives/decorators/bootstrap/';
163+
164+
decoratorsProvider.createDecorator('bootstrapDecorator', {
165+
textarea: base + 'textarea.html',
166+
fieldset: base + 'fieldset.html',
167+
array: base + 'array.html',
168+
tabarray: base + 'tabarray.html',
169+
tabs: base + 'tabs.html',
170+
section: base + 'section.html',
171+
conditional: base + 'section.html',
172+
actions: base + 'actions.html',
173+
select: base + 'select.html',
174+
checkbox: base + 'checkbox.html',
175+
checkboxes: base + 'checkboxes.html',
176+
number: base + 'default.html',
177+
password: base + 'default.html',
178+
submit: base + 'submit.html',
179+
button: base + 'submit.html',
180+
radios: base + 'radios.html',
181+
'radios-inline': base + 'radios-inline.html',
182+
radiobuttons: base + 'radio-buttons.html',
183+
help: base + 'help.html',
184+
'default': base + 'default.html'
185+
}, [
186+
function(form) {
187+
if (form.readonly && form.key && form.type !== 'fieldset') {
188+
return base + 'readonly.html';
189+
}
190+
}
191+
]);
192+
```
193+
`decoratorsProvider.createDecorator(name, mapping, rules)` takes a name argument, a mapping object
194+
(type -> template) and an optional list of rule functions.
195+
196+
When the decorator is trying to match a form type against a tempate it first executes all the rules
197+
in order. If one returns that is used as template, otherwise it checks the mappings.

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Documentation
2525
1. [array](#array)
2626
1. [tabarray](#tabarray)
2727
1. [Post process function](#post-process-function)
28+
1. [Extending Schema Form](extending.md)
2829

2930
Basic Usage
3031
-----------

0 commit comments

Comments
 (0)