Skip to content

Commit a382fd5

Browse files
committed
gulped
1 parent b0028fc commit a382fd5

File tree

2 files changed

+288
-13
lines changed

2 files changed

+288
-13
lines changed

dist/schema-form.js

+287-12
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,60 @@ angular.module('schemaForm').provider('sfBuilder', ['sfPathProvider', function(s
242242
}
243243
}
244244
}
245+
},
246+
condition: function(args) {
247+
// Do we have a condition? Then we slap on an ng-if on all children,
248+
// but be nice to existing ng-if.
249+
if (args.form.condition) {
250+
var evalExpr = 'evalExpr(' + args.path +
251+
'.contidion, { model: model, "arrayIndex": $index})';
252+
if (args.form.key) {
253+
var strKey = sfPathProvider.stringify(args.form.key);
254+
evalExpr = 'evalExpr(' + args.path + '.condition,{ model: model, "arrayIndex": $index, ' +
255+
'"modelValue": model' + (strKey[0] === '[' ? '' : '.') + strKey + '})';
256+
}
257+
258+
var children = args.fieldFrag.children;
259+
for (var i = 0; i < children.length; i++) {
260+
var child = children[i];
261+
var ngIf = child.getAttribute('ng-if');
262+
child.setAttribute(
263+
'ng-if',
264+
ngIf ?
265+
'(' + ngIf +
266+
') || (' + evalExpr + ')'
267+
: evalExpr
268+
);
269+
}
270+
}
271+
},
272+
array: function(args) {
273+
var items = args.fieldFrag.querySelector('[schema-form-array-items]');
274+
if (items) {
275+
state = angular.copy(args.state);
276+
state.keyRedaction = state.keyRedaction || 0;
277+
state.keyRedaction += args.form.key.length + 1;
278+
279+
// Special case, an array with just one item in it that is not an object.
280+
// So then we just override the modelValue
281+
if (args.form.schema && args.form.schema.items &&
282+
args.form.schema.items.type &&
283+
args.form.schema.items.type.indexOf('object') === -1 &&
284+
args.form.schema.items.type.indexOf('array') === -1) {
285+
var strKey = sfPathProvider.stringify(args.form.key).replace(/"/g, '&quot;') + '[$index]';
286+
state.modelValue = 'modelArray[$index]';
287+
} else {
288+
state.modelName = 'item';
289+
}
290+
291+
// Flag to the builder that where in an array.
292+
// This is needed for compatabiliy if a "old" add-on is used that
293+
// hasn't been transitioned to the new builder.
294+
state.arrayCompatFlag = true;
295+
296+
var childFrag = args.build(args.form.items, args.path + '.items', state);
297+
items.appendChild(childFrag);
298+
}
245299
}
246300
};
247301
this.builders = builders;
@@ -279,12 +333,20 @@ angular.module('schemaForm').provider('sfBuilder', ['sfPathProvider', function(s
279333
if (!field.replace) {
280334
// Backwards compatability build
281335
var n = document.createElement(snakeCase(decorator.__name, '-'));
282-
n.setAttribute('form', path + '[' + index + ']');
336+
if (state.arrayCompatFlag) {
337+
n.setAttribute('form','copyWithIndex($index)');
338+
} else {
339+
n.setAttribute('form', path + '[' + index + ']');
340+
}
341+
283342
(checkForSlot(f, slots) || frag).appendChild(n);
284343

285344
} else {
286345
var tmpl;
287346

347+
// Reset arrayCompatFlag, it's only valid for direct children of the array.
348+
state.arrayCompatFlag = false;
349+
288350
// TODO: Create a couple fo testcases, small and large and
289351
// measure optmization. A good start is probably a cache of DOM nodes for a particular
290352
// template that can be cloned instead of using innerHTML
@@ -330,17 +392,17 @@ angular.module('schemaForm').provider('sfBuilder', ['sfPathProvider', function(s
330392
};
331393

332394
return {
333-
/**
334-
* Builds a form from a canonical form definition
335-
*/
336-
build: function(form, decorator, slots, lookup) {
337-
return build(form, decorator, function(url) {
338-
return $templateCache.get(url);
339-
}, slots, undefined, undefined, lookup);
340-
341-
},
342-
builder: builders,
343-
internalBuild: build
395+
/**
396+
* Builds a form from a canonical form definition
397+
*/
398+
build: function(form, decorator, slots, lookup) {
399+
return build(form, decorator, function(url) {
400+
return $templateCache.get(url);
401+
}, slots, undefined, undefined, lookup);
402+
403+
},
404+
builder: builders,
405+
internalBuild: build
344406
};
345407
}];
346408

@@ -1494,6 +1556,7 @@ angular.module('schemaForm').factory('sfValidator', [function() {
14941556

14951557
/**
14961558
* Directive that handles the model arrays
1559+
* DEPRECATED with the new builder use the sfNewArray instead.
14971560
*/
14981561
angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sfValidator', 'sfPath',
14991562
function(sfSelect, schemaForm, sfValidator, sfPath) {
@@ -2087,6 +2150,218 @@ angular.module('schemaForm').directive('sfMessage',
20872150
};
20882151
}]);
20892152

2153+
/**
2154+
* Directive that handles the model arrays
2155+
*/
2156+
angular.module('schemaForm').directive('sfNewArray', ['sfSelect', 'sfPath', 'schemaForm',
2157+
function(sel, sfPath, schemaForm) {
2158+
return {
2159+
scope: false,
2160+
link: function(scope, element, attrs) {
2161+
scope.min = 0;
2162+
2163+
scope.modelArray = scope.$eval(attrs.sfNewArray);
2164+
2165+
// We need to have a ngModel to hook into validation. It doesn't really play well with
2166+
// arrays though so we both need to trigger validation and onChange.
2167+
// So we watch the value as well. But watching an array can be tricky. We wan't to know
2168+
// when it changes so we can validate,
2169+
var watchFn = function() {
2170+
//scope.modelArray = modelArray;
2171+
scope.modelArray = scope.$eval(attrs.sfNewArray);
2172+
// validateField method is exported by schema-validate
2173+
if (scope.validateField) {
2174+
scope.validateField();
2175+
}
2176+
};
2177+
2178+
var onChangeFn = function() {
2179+
if (scope.form && scope.form.onChange) {
2180+
if (angular.isFunction(scope.form.onChange)) {
2181+
scope.form.onChange(scope.modelArray, scope.form);
2182+
} else {
2183+
scope.evalExpr(scope.form.onChange, {'modelValue': scope.modelArray, form: scope.form});
2184+
}
2185+
}
2186+
};
2187+
2188+
// We need the form definition to make a decision on how we should listen.
2189+
var once = scope.$watch('form', function(form) {
2190+
if (!form) {
2191+
return;
2192+
}
2193+
2194+
// Always start with one empty form unless configured otherwise.
2195+
// Special case: don't do it if form has a titleMap
2196+
if (!form.titleMap && form.startEmpty !== true && (!scope.modelArray || scope.modelArray.length === 0)) {
2197+
scope.appendToArray();
2198+
}
2199+
2200+
// If we have "uniqueItems" set to true, we must deep watch for changes.
2201+
if (scope.form && scope.form.schema && scope.form.schema.uniqueItems === true) {
2202+
scope.$watch(attrs.sfNewArray, watchFn, true);
2203+
2204+
// We still need to trigger onChange though.
2205+
scope.$watch([attrs.sfNewArray, attrs.sfNewArray + '.length'], onChangeFn);
2206+
2207+
} else {
2208+
// Otherwise we like to check if the instance of the array has changed, or if something
2209+
// has been added/removed.
2210+
if (scope.$watchGroup) {
2211+
scope.$watchGroup([attrs.sfNewArray, attrs.sfNewArray + '.length'], function() {
2212+
watchFn();
2213+
onChangeFn();
2214+
});
2215+
} else {
2216+
// Angular 1.2 support
2217+
scope.$watch(attrs.sfNewArray, function() {
2218+
watchFn();
2219+
onChangeFn();
2220+
});
2221+
scope.$watch(attrs.sfNewArray + '.length', function() {
2222+
watchFn();
2223+
onChangeFn();
2224+
});
2225+
}
2226+
}
2227+
2228+
// Title Map handling
2229+
// If form has a titleMap configured we'd like to enable looping over
2230+
// titleMap instead of modelArray, this is used for intance in
2231+
// checkboxes. So instead of variable number of things we like to create
2232+
// a array value from a subset of values in the titleMap.
2233+
// The problem here is that ng-model on a checkbox doesn't really map to
2234+
// a list of values. This is here to fix that.
2235+
if (form.titleMap && form.titleMap.length > 0) {
2236+
scope.titleMapValues = [];
2237+
2238+
// We watch the model for changes and the titleMapValues to reflect
2239+
// the modelArray
2240+
var updateTitleMapValues = function(arr) {
2241+
scope.titleMapValues = [];
2242+
arr = arr || [];
2243+
2244+
form.titleMap.forEach(function(item) {
2245+
scope.titleMapValues.push(arr.indexOf(item.value) !== -1);
2246+
});
2247+
};
2248+
//Catch default values
2249+
updateTitleMapValues(scope.modelArray);
2250+
2251+
// TODO: Refactor and see if we can get rid of this watch by piggy backing on the
2252+
// validation watch.
2253+
scope.$watchCollection('modelArray', updateTitleMapValues);
2254+
2255+
//To get two way binding we also watch our titleMapValues
2256+
scope.$watchCollection('titleMapValues', function(vals, old) {
2257+
if (vals && vals !== old) {
2258+
var arr = scope.modelArray;
2259+
2260+
// Apparently the fastest way to clear an array, readable too.
2261+
// http://jsperf.com/array-destroy/32
2262+
while (arr.length > 0) {
2263+
arr.pop();
2264+
}
2265+
form.titleMap.forEach(function(item, index) {
2266+
if (vals[index]) {
2267+
arr.push(item.value);
2268+
}
2269+
});
2270+
2271+
// Time to validate the rebuilt array.
2272+
// validateField method is exported by schema-validate
2273+
if (scope.validateField) {
2274+
scope.validateField();
2275+
}
2276+
}
2277+
});
2278+
}
2279+
2280+
once();
2281+
});
2282+
2283+
scope.appendToArray = function() {
2284+
2285+
var empty;
2286+
2287+
// Same old add empty things to the array hack :(
2288+
if (scope.form && scope.form.schema) {
2289+
if (scope.form.schema.items) {
2290+
if (scope.form.schema.items.type === 'object') {
2291+
empty = {};
2292+
} else if (scope.form.schema.items.type === 'array') {
2293+
empty = [];
2294+
}
2295+
}
2296+
}
2297+
2298+
var model = scope.modelArray;
2299+
if (!model) {
2300+
// Create and set an array if needed.
2301+
var selection = sfPath.parse(attrs.sfNewArray);
2302+
model = [];
2303+
sel(selection, scope, model);
2304+
scope.modelArray = model;
2305+
}
2306+
model.push(empty);
2307+
2308+
return model;
2309+
};
2310+
2311+
scope.deleteFromArray = function(index) {
2312+
var model = scope.modelArray;
2313+
if (model) {
2314+
model.splice(index, 1);
2315+
}
2316+
return model;
2317+
};
2318+
2319+
// For backwards compatability, i.e. when a bootstrap-decorator tag is used
2320+
// as child to the array.
2321+
var setIndex = function(index) {
2322+
return function(form) {
2323+
if (form.key) {
2324+
form.key[form.key.indexOf('')] = index;
2325+
}
2326+
};
2327+
};
2328+
var formDefCache = {};
2329+
scope.copyWithIndex = function(index) {
2330+
var form = scope.form;
2331+
if (!formDefCache[index]) {
2332+
2333+
// To be more compatible with JSON Form we support an array of items
2334+
// in the form definition of "array" (the schema just a value).
2335+
// for the subforms code to work this means we wrap everything in a
2336+
// section. Unless there is just one.
2337+
var subForm = form.items[0];
2338+
if (form.items.length > 1) {
2339+
subForm = {
2340+
type: 'section',
2341+
items: form.items.map(function(item) {
2342+
item.ngModelOptions = form.ngModelOptions;
2343+
if (angular.isUndefined(item.readonly)) {
2344+
item.readonly = form.readonly;
2345+
}
2346+
return item;
2347+
})
2348+
};
2349+
}
2350+
2351+
if (subForm) {
2352+
var copy = angular.copy(subForm);
2353+
copy.arrayIndex = index;
2354+
schemaForm.traverseForm(copy, setIndex(index));
2355+
formDefCache[index] = copy;
2356+
}
2357+
}
2358+
return formDefCache[index];
2359+
};
2360+
2361+
}
2362+
};
2363+
}]);
2364+
20902365
/*
20912366
FIXME: real documentation
20922367
<form sf-form="form" sf-schema="schema" sf-decorator="foobar"></form>

0 commit comments

Comments
 (0)