merlin/merlin/static/merlin/js/merlin.field.models.js

306 lines
8.9 KiB
JavaScript

(function() {
'use strict';
angular
.module('merlin')
.factory('merlin.field.models', merlinFieldModels);
merlinFieldModels.$inject = ['merlin.utils', 'merlin.panel.models', '$http'];
function merlinFieldModels(utils, panels, $http) {
var wildcardMixin = Barricade.Blueprint.create(function() {
return this;
});
var viewChoicesMixin = Barricade.Blueprint.create(function() {
var self = this;
var dropDownLimit = this._dropDownLimit || 5;
var values, labels, items, isDropDown;
function fillItems() {
values = self.getEnumValues();
labels = self.getEnumLabels();
items = {};
if (values) {
values.forEach(function (value, index) {
items[value] = labels[index];
});
}
}
this.getLabel = function(value) {
if ( angular.isUndefined(values) ) {
fillItems();
}
return items[value];
};
this.getValues = function() {
if ( angular.isUndefined(values) ) {
fillItems();
}
return values;
};
this.resetValues = function() {
values = undefined;
};
this.isDropDown = function() {
// what starts its life as being dropdown / not being dropdown
// should remain so forever
if ( angular.isUndefined(isDropDown) ) {
isDropDown = !this.isEmpty() && this.getValues().length < dropDownLimit;
}
return isDropDown;
};
this.setType('choices');
return this;
});
/* Html renderer helper. The main idea is that fields with simple (or plain)
structure (i.e. Atomics = string | number | text | boolean and list or
dictionary containing just Atomics) could be rendered in one column, while
fields with non plain structure should be rendered in two columns.
*/
var plainStructureMixin = Barricade.Blueprint.create(function() {
this.isPlainStructure = function() {
if (this.getType() == 'frozendict') {
return false;
}
if (!this.instanceof(Barricade.Arraylike) || !this.length()) {
return false;
}
return !this.get(0).instanceof(Barricade.Container);
};
return this;
});
var modelMixin = Barricade.Blueprint.create(function(type) {
var isValid = true;
var isValidatable = false;
this.value = function() {
if ( !arguments.length ) {
if ( isValidatable ) {
return isValid ? this.get() : undefined;
} else {
return this.get();
}
} else {
this.set(arguments[0]);
isValid = !this.hasError();
}
};
this.id = utils.getNewId();
this.getType = function() {
return type;
};
this.setValidatable = function(validatable) {
isValidatable = validatable;
};
this.setType = function(_type) {
type = _type;
};
this.isAdditive = function() {
return this.instanceof(Barricade.Arraylike);
};
this.isAtomic = function() {
return !this.instanceof(Barricade.Container);
};
this.title = function() {
var title = utils.getMeta(this, 'title');
if ( !title ) {
if ( this.instanceof(Barricade.ImmutableObject) ) {
if ( this.getKeys().indexOf('name') > -1 ) {
return this.get('name').get();
}
}
title = utils.makeTitle(this.getID()) || '';
}
return title;
};
wildcardMixin.call(this);
if ( this.getEnumValues ) {
viewChoicesMixin.call(this);
}
return this;
});
function meldGroup() {
if ( utils.getMeta(this, 'group') ) {
panels.groupmixin.call(this);
}
}
var stringModel = Barricade.Primitive.extend({
create: function(json, parameters) {
var self = Barricade.Primitive.create.call(this, json, parameters);
return modelMixin.call(self, 'string');
}
}, {'@type': String});
var textModel = Barricade.Primitive.extend({
create: function(json, parameters) {
var self = Barricade.Primitive.create.call(this, json, parameters);
return modelMixin.call(self, 'text');
}
}, {'@type': String});
var numberModel = Barricade.Primitive.extend({
create: function(json, parameters) {
var self = Barricade.Primitive.create.call(this, json, parameters);
return modelMixin.call(self, 'number');
}
}, {'@type': Number});
var listModel = Barricade.Array.extend({
create: function(json, parameters) {
var self = Barricade.Array.create.call(this, json, parameters);
modelMixin.call(self, 'list');
self.add = function() {
self.push(undefined, parameters);
};
meldGroup.call(self);
plainStructureMixin.call(self);
return self;
}
}, {'@type': Array});
var frozendictModel = Barricade.ImmutableObject.extend({
create: function(json, parameters) {
var self = Barricade.ImmutableObject.create.call(this, json, parameters);
modelMixin.call(self, 'frozendict');
meldGroup.call(self);
plainStructureMixin.call(self);
return self;
}
}, {'@type': Object});
var dictionaryModel = Barricade.MutableObject.extend({
create: function(json, parameters) {
var self = Barricade.MutableObject.create.call(this, json, parameters);
var _elClass = self._elementClass;
var baseKey = utils.getMeta(_elClass, 'baseKey') || 'key';
var baseName = utils.getMeta(_elClass, 'baseName') || utils.makeTitle(baseKey);
modelMixin.call(self, 'dictionary');
plainStructureMixin.call(self);
function initKeyAccessor(value) {
value.keyValue = function () {
if ( arguments.length ) {
value.setID(arguments[0]);
} else {
return value.getID();
}
};
}
self.each(function(key, value) {
initKeyAccessor(value);
}).on('change', function(op, index) {
if (op === 'add' || op === 'set') {
initKeyAccessor(self.get(index));
}
});
self.add = function(newID) {
var regexp = new RegExp('(' + baseKey + ')([0-9]+)');
var newValue;
newID = newID || baseKey + utils.getNextIDSuffix(self, regexp);
if ( _elClass.instanceof(Barricade.ImmutableObject) ) {
if ( 'name' in _elClass._schema ) {
var nameNum = utils.getNextIDSuffix(self, regexp);
newValue = {name: baseName + nameNum};
} else {
newValue = {};
}
} else { // usually, it's either frozendict inside or string
newValue = '';
}
self.push(newValue, utils.extend(self._parameters, {id: newID}));
};
self.empty = function() {
for ( var i = this._data.length; i > 0; i-- ) {
self.remove(i - 1);
}
};
self.resetKeys = function(keys) {
self.empty();
keys.forEach(function(key) {
self.push(undefined, {id: key});
});
};
self.removeItem = function(key) {
self.remove(self.getPosByID(key));
};
meldGroup.call(self);
return self;
}
}, {'@type': Object});
var linkedCollectionModel = stringModel.extend({
create: function(json, parameters) {
var self = stringModel.create.call(this, json, parameters);
var collectionCls = Barricade.create({
'@type': String,
'@ref': {
to: function() {
return parameters.toCls;
},
needs: function() {
return parameters.neededCls;
},
getter: function(data) {
return data.needed.get(parameters.substitutedEntryID);
}
}
});
self._collection = collectionCls.create().on(
'replace', function(newValue) {
self._collection = newValue;
self._collection.on('change', function() {
self._choices = self._collection.getIDs();
self.resetValues();
});
self._collection.emit('change');
});
return self;
},
_choices: []
}, {
'@enum': function() {
if ( this._collection.isPlaceholder() ) {
this.emit('_resolveUp', this._collection);
}
return this._choices;
}
}
);
return {
string: stringModel,
text: textModel,
number: numberModel,
list: listModel,
linkedcollection: linkedCollectionModel,
dictionary: dictionaryModel,
frozendict: frozendictModel,
wildcard: wildcardMixin // use for most general type-checks
};
}
})();