Skip to content

Commit 11d329c

Browse files
committed
Introducing builder function
First steps towards a better building infra structure that can build the entire form in one go instead relying on $compile in link phase. Fully backwards compatible.
1 parent b4d01af commit 11d329c

File tree

4 files changed

+246
-97
lines changed

4 files changed

+246
-97
lines changed

src/directives/decorators/bootstrap/bootstrap-decorator.js

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,32 @@
11
angular.module('schemaForm').config(['schemaFormDecoratorsProvider', function(decoratorsProvider) {
22
var base = 'directives/decorators/bootstrap/';
33

4-
decoratorsProvider.createDecorator('bootstrapDecorator', {
5-
textarea: base + 'textarea.html',
6-
fieldset: base + 'fieldset.html',
7-
array: base + 'array.html',
8-
tabarray: base + 'tabarray.html',
9-
tabs: base + 'tabs.html',
10-
section: base + 'section.html',
11-
conditional: base + 'section.html',
12-
actions: base + 'actions.html',
13-
select: base + 'select.html',
14-
checkbox: base + 'checkbox.html',
15-
checkboxes: base + 'checkboxes.html',
16-
number: base + 'default.html',
17-
password: base + 'default.html',
18-
submit: base + 'submit.html',
19-
button: base + 'submit.html',
20-
radios: base + 'radios.html',
21-
'radios-inline': base + 'radios-inline.html',
22-
radiobuttons: base + 'radio-buttons.html',
23-
help: base + 'help.html',
24-
'default': base + 'default.html'
4+
decoratorsProvider.defineDecorator('bootstrapDecorator', {
5+
textarea: {template: base + 'textarea.html', replace: false},
6+
fieldset: {template: base + 'fieldset.html', replace: false},
7+
/*fieldset: {template: base + 'fieldset.html', replace: true, builder: function(args) {
8+
var children = args.build(args.form.items, args.path + '.items');
9+
console.log('fieldset children frag', children.childNodes)
10+
args.fieldFrag.appendChild(children);
11+
}},*/
12+
array: {template: base + 'array.html', replace: false},
13+
tabarray: {template: base + 'tabarray.html', replace: false},
14+
tabs: {template: base + 'tabs.html', replace: false},
15+
section: {template: base + 'section.html', replace: false},
16+
conditional: {template: base + 'section.html', replace: false},
17+
actions: {template: base + 'actions.html', replace: false},
18+
select: {template: base + 'select.html', replace: false},
19+
checkbox: {template: base + 'checkbox.html', replace: false},
20+
checkboxes: {template: base + 'checkboxes.html', replace: false},
21+
number: {template: base + 'default.html', replace: false},
22+
password: {template: base + 'default.html', replace: false},
23+
submit: {template: base + 'submit.html', replace: false},
24+
button: {template: base + 'submit.html', replace: false},
25+
radios: {template: base + 'radios.html', replace: false},
26+
'radios-inline': {template: base + 'radios-inline.html', replace: false},
27+
radiobuttons: {template: base + 'radio-buttons.html', replace: false},
28+
help: {template: base + 'help.html', replace: false},
29+
'default': {template: base + 'default.html', replace: false}
2530
}, []);
2631

2732
//manual use directives

src/directives/schema-form.js

Lines changed: 6 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,8 @@ FIXME: real documentation
55

66
angular.module('schemaForm')
77
.directive('sfSchema',
8-
['$compile', 'schemaForm', 'schemaFormDecorators', 'sfSelect', 'sfPath',
9-
function($compile, schemaForm, schemaFormDecorators, sfSelect, sfPath) {
10-
11-
var SNAKE_CASE_REGEXP = /[A-Z]/g;
12-
var snakeCase = function(name, separator) {
13-
separator = separator || '_';
14-
return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
15-
return (pos ? separator : '') + letter.toLowerCase();
16-
});
17-
};
8+
['$compile', 'schemaForm', 'schemaFormDecorators', 'sfSelect', 'sfPath', 'sfBuilder',
9+
function($compile, schemaForm, schemaFormDecorators, sfSelect, sfPath, sfBuilder) {
1810

1911
return {
2012
scope: {
@@ -65,7 +57,6 @@ angular.module('schemaForm')
6557
// Common renderer function, can either be triggered by a watch or by an event.
6658
var render = function(schema, form) {
6759
var merged = schemaForm.merge(schema, form, ignore, scope.options);
68-
var frag = document.createDocumentFragment();
6960

7061
// Create a new form and destroy the old one.
7162
// Not doing keeps old form elements hanging around after
@@ -93,30 +84,11 @@ angular.module('schemaForm')
9384
slots[slotsFound[i].getAttribute('sf-insert-field')] = slotsFound[i];
9485
}
9586

96-
//Create directives from the form definition
97-
angular.forEach(merged, function(obj, i) {
98-
var n = document.createElement(attrs.sfUseDecorator ||
99-
snakeCase(schemaFormDecorators.defaultDecorator, '-'));
100-
n.setAttribute('form', 'schemaForm.form[' + i + ']');
101-
102-
// Check if there is a slot to put this in...
103-
if (obj.key) {
104-
var slot = slots[sfPath.stringify(obj.key)];
105-
if (slot) {
106-
while (slot.firstChild) {
107-
slot.removeChild(slot.firstChild);
108-
}
109-
slot.appendChild(n);
110-
return;
111-
}
112-
}
113-
114-
// ...otherwise add it to the frag
115-
frag.appendChild(n);
116-
117-
});
87+
// if sfUseDecorator is undefined the default decorator is used.
88+
var decorator = schemaFormDecorators.decorator(attrs.sfUseDecorator);
11889

119-
element[0].appendChild(frag);
90+
// Use the builder to build it and append the result
91+
element[0].appendChild( sfBuilder.build(merged, decorator, slots) );
12092

12193
//compile only children
12294
$compile(element.children())(childScope);

src/services/builder.js

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
2+
// FIXME: type template (using custom builder)
3+
angular.module('schemaForm').factory('sfBuilder',
4+
['$templateCache', 'schemaFormDecorators', 'sfPath', function($templateCache, schemaFormDecorators, sfPath) {
5+
6+
var SNAKE_CASE_REGEXP = /[A-Z]/g;
7+
var snakeCase = function(name, separator) {
8+
separator = separator || '_';
9+
return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
10+
return (pos ? separator : '') + letter.toLowerCase();
11+
});
12+
};
13+
14+
15+
var checkForSlot = function(form, slots) {
16+
// Finally append this field to the frag.
17+
// Check for slots
18+
if (form.key) {
19+
var slot = slots[sfPath.stringify(form.key)];
20+
console.log('checking for slots!', sfPath.stringify(form.key), slot)
21+
if (slot) {
22+
console.log('clearing and appending to slot')
23+
while (slot.firstChild) {
24+
slot.removeChild(slot.firstChild);
25+
}
26+
return slot;
27+
}
28+
}
29+
};
30+
31+
32+
var build = function(items, decorator, templateFn, slots, path) {
33+
path = path || 'schemaForm.form';
34+
var container = document.createDocumentFragment();
35+
items.reduce(function(frag, f, index) {
36+
37+
// Sanity check.
38+
if (!f.type) {
39+
return;
40+
}
41+
42+
var field = decorator[f.type] || decorator['default'];
43+
if (!field.replace) {
44+
// Backwards compatability build
45+
var n = document.createElement(snakeCase(decorator.__name, '-'));
46+
n.setAttribute('form', path + '[' + index + ']');
47+
(checkForSlot(f, slots) || frag).appendChild(n);
48+
49+
} else {
50+
var tmpl;
51+
52+
// TODO: Create a couple fo testcases, small and large and
53+
// measure optmization. A good start is probably a cache of DOM nodes for a particular
54+
// template that can be cloned instead of using innerHTML
55+
var div = document.createElement('div');
56+
var template = templateFn(field.template) || templateFn([decorator['default'].template]);
57+
if (f.key) {
58+
var key = f.key ?
59+
sfPath.stringify(f.key).replace(/"/g, '"') : '';
60+
template = template.replace(
61+
/\$\$value\$\$/g,
62+
'model' + (key[0] !== '[' ? '.' : '') + key
63+
);
64+
}
65+
66+
div.innerHTML = template;
67+
tmpl = document.createDocumentFragment();
68+
69+
var len = div.childNodes.length;
70+
for (var i = 0; i < len; i++) {
71+
tmpl.appendChild(div.childNodes[i]);
72+
}
73+
74+
tmpl.firstChild.setAttribute('sf-field',path + '[' + index + ']');
75+
76+
// Possible builder, often a noop
77+
field.builder({
78+
fieldFrag: tmpl,
79+
form: f,
80+
path: path + '[' + index + ']',
81+
82+
// Recursive build fn
83+
build: function(items, path) {
84+
return build(items, decorator, templateFn, slots, path);
85+
},
86+
87+
});
88+
89+
// Append
90+
(checkForSlot(f, slots) || frag).appendChild(tmpl);
91+
}
92+
return frag;
93+
}, container);
94+
95+
return container;
96+
};
97+
98+
99+
/* FIXME: make a utility function of this ordinary case
100+
var transclusion = function() {
101+
// We might be able to micro optimize here with some kind of setting
102+
// or by checking the schema for the type (when we have those.)
103+
// but a quick jsperf did 55 000 querySelectorAll per second (on my laptop),
104+
// so I think this isn't the main performance hog.
105+
var transclusions = tmpl.querySelectorAll('[sf-transclude]');
106+
107+
if (transclusions.length) {
108+
// Before we do any transclusion we need clone the cache for later use, but just the first time.
109+
if ([f.type] === tmpl) {
110+
[f.type] = [f.type].cloneNode(true);
111+
}
112+
113+
for (var i = 0; i < transclusions.length; i++) {
114+
var n = transclusions[i];
115+
116+
// The sf-transclude attribute is not a directive, but has the name of what we're supposed to
117+
// traverse. FIXME: Tabs? How do we loop over something that is not a list of forms?
118+
// maybe callback?
119+
var sub = form[n.getAttribute('sf-transclude')];
120+
if (sub) {
121+
sub = Array.isArray(sub) ? sub : [sub];
122+
123+
// Build the subform recursivly
124+
n.appendChild( build(sub, templates, ) );
125+
126+
}
127+
}
128+
}
129+
130+
}*/
131+
132+
133+
var builder = {
134+
/**
135+
* Builds a form from a canonical form definition
136+
*/
137+
build: function(form, decorator, slots) {
138+
console.warn(slots)
139+
return build(form, decorator, function(url) {
140+
return $templateCache.get(url) || '';
141+
}, slots);
142+
143+
},
144+
internalBuild: build
145+
};
146+
return builder;
147+
148+
}]);

0 commit comments

Comments
 (0)