/**
* Directive that handles the model arrays
*/
angular.module('schemaForm').directive('sfNewArray', ['sfSelect', 'sfPath', 'schemaForm',
function(sel, sfPath, schemaForm) {
return {
scope: false,
link: function(scope, element, attrs) {
scope.min = 0;
scope.modelArray = scope.$eval(attrs.sfNewArray);
// We need to have a ngModel to hook into validation. It doesn't really play well with
// arrays though so we both need to trigger validation and onChange.
// So we watch the value as well. But watching an array can be tricky. We wan't to know
// when it changes so we can validate,
var watchFn = function() {
//scope.modelArray = modelArray;
scope.modelArray = scope.$eval(attrs.sfNewArray);
// validateField method is exported by schema-validate
Eif (scope.validateField) {
scope.validateField();
}
};
var onChangeFn = function() {
Iif (scope.form && scope.form.onChange) {
if (angular.isFunction(scope.form.onChange)) {
scope.form.onChange(scope.modelArray, scope.form);
} else {
scope.evalExpr(scope.form.onChange, {'modelValue': scope.modelArray, form: scope.form});
}
}
};
// We need the form definition to make a decision on how we should listen.
var once = scope.$watch('form', function(form) {
Iif (!form) {
return;
}
// Always start with one empty form unless configured otherwise.
// Special case: don't do it if form has a titleMap
Iif (!form.titleMap && form.startEmpty !== true && (!scope.modelArray || scope.modelArray.length === 0)) {
scope.appendToArray();
}
// If we have "uniqueItems" set to true, we must deep watch for changes.
Iif (scope.form && scope.form.schema && scope.form.schema.uniqueItems === true) {
scope.$watch(attrs.sfNewArray, watchFn, true);
// We still need to trigger onChange though.
scope.$watch([attrs.sfNewArray, attrs.sfNewArray + '.length'], onChangeFn);
} else {
// Otherwise we like to check if the instance of the array has changed, or if something
// has been added/removed.
Eif (scope.$watchGroup) {
scope.$watchGroup([attrs.sfNewArray, attrs.sfNewArray + '.length'], function() {
watchFn();
onChangeFn();
});
} else {
// Angular 1.2 support
scope.$watch(attrs.sfNewArray, function() {
watchFn();
onChangeFn();
});
scope.$watch(attrs.sfNewArray + '.length', function() {
watchFn();
onChangeFn();
});
}
}
// Title Map handling
// If form has a titleMap configured we'd like to enable looping over
// titleMap instead of modelArray, this is used for intance in
// checkboxes. So instead of variable number of things we like to create
// a array value from a subset of values in the titleMap.
// The problem here is that ng-model on a checkbox doesn't really map to
// a list of values. This is here to fix that.
Iif (form.titleMap && form.titleMap.length > 0) {
scope.titleMapValues = [];
// We watch the model for changes and the titleMapValues to reflect
// the modelArray
var updateTitleMapValues = function(arr) {
scope.titleMapValues = [];
arr = arr || [];
form.titleMap.forEach(function(item) {
scope.titleMapValues.push(arr.indexOf(item.value) !== -1);
});
};
//Catch default values
updateTitleMapValues(scope.modelArray);
// TODO: Refactor and see if we can get rid of this watch by piggy backing on the
// validation watch.
scope.$watchCollection('modelArray', updateTitleMapValues);
//To get two way binding we also watch our titleMapValues
scope.$watchCollection('titleMapValues', function(vals, old) {
if (vals && vals !== old) {
var arr = scope.modelArray;
// Apparently the fastest way to clear an array, readable too.
// http://jsperf.com/array-destroy/32
while (arr.length > 0) {
arr.pop();
}
form.titleMap.forEach(function(item, index) {
if (vals[index]) {
arr.push(item.value);
}
});
// Time to validate the rebuilt array.
// validateField method is exported by schema-validate
if (scope.validateField) {
scope.validateField();
}
}
});
}
once();
});
scope.appendToArray = function() {
var empty;
// Same old add empty things to the array hack :(
if (scope.form && scope.form.schema) {
if (scope.form.schema.items) {
if (scope.form.schema.items.type === 'object') {
empty = {};
} else if (scope.form.schema.items.type === 'array') {
empty = [];
}
}
}
var model = scope.modelArray;
if (!model) {
// Create and set an array if needed.
var selection = sfPath.parse(attrs.sfNewArray);
model = [];
sel(selection, scope, model);
scope.modelArray = model;
}
model.push(empty);
return model;
};
scope.deleteFromArray = function(index) {
var model = scope.modelArray;
if (model) {
model.splice(index, 1);
}
return model;
};
// For backwards compatability, i.e. when a bootstrap-decorator tag is used
// as child to the array.
var setIndex = function(index) {
return function(form) {
if (form.key) {
form.key[form.key.indexOf('')] = index;
}
};
};
var formDefCache = {};
scope.copyWithIndex = function(index) {
var form = scope.form;
if (!formDefCache[index]) {
// To be more compatible with JSON Form we support an array of items
// in the form definition of "array" (the schema just a value).
// for the subforms code to work this means we wrap everything in a
// section. Unless there is just one.
var subForm = form.items[0];
if (form.items.length > 1) {
subForm = {
type: 'section',
items: form.items.map(function(item) {
item.ngModelOptions = form.ngModelOptions;
if (angular.isUndefined(item.readonly)) {
item.readonly = form.readonly;
}
return item;
})
};
}
if (subForm) {
var copy = angular.copy(subForm);
copy.arrayIndex = index;
schemaForm.traverseForm(copy, setIndex(index));
formDefCache[index] = copy;
}
}
return formDefCache[index];
};
}
};
}]);
|