Change code to satisfy new ESLint checks

Change-Id: I36926039fff61328626682f37eb229f58e27c928
This commit is contained in:
Vlad Okhrimenko 2015-07-07 15:43:11 +03:00 committed by Timur Sufiev
parent 6062444015
commit ac6336b5e2
8 changed files with 794 additions and 731 deletions

View File

@ -3,170 +3,201 @@
*/ */
(function() { (function() {
'use strict'; 'use strict';
angular
angular.module('merlin') .module('merlin')
/* /*
* Allows to edit field name in-place. * Allows to edit field name in-place.
* For example, you have a field named 'Input 1' and you want to replace this name with "Base input" value. * For example, you have a field named 'Input 1' and you want to replace this name with
* "Base input" value.
* If you add editable directive to such field, you will get a marker icon near this field, * If you add editable directive to such field, you will get a marker icon near this field,
* and with clicking on this icon you can type new name and save or discard changes. * and with clicking on this icon you can type new name and save or discard changes.
* */ * */
.directive('editable', function() { .directive('editable', editable)
return {
restrict: 'E',
templateUrl: '/static/merlin/templates/editable.html',
require: 'ngModel',
scope: true,
link: function(scope, element, attrs, ngModelCtrl) {
var hiddenSpan = element.find('span.width-detector'),
input = element.find('input'),
maxWidth = 400;
function adjustWidth() { /*
var width; * this directive auto-sets the focus to an input field once it is shown.
hiddenSpan.html(scope.editableValue); * */
width = hiddenSpan.width(); .directive('showFocus', showFocus)
input.width(width <= maxWidth ? width : maxWidth);
}
function accept() { /*
ngModelCtrl.$setViewValue(scope.editableValue); * tells Merlin to render this element as a panel.
scope.isEdited = false; * */
} .directive('panel', panel)
function reject() { /*
ngModelCtrl.$rollbackViewValue(); * tells Merlin to render this element as a group with ability to collapse.
scope.isEdited = false; * */
} .directive('collapsibleGroup', collapsibleGroup)
/*
* sets up the DOM nodes related to validation of model being edited in this widget
* (and specifies the name of this model on scope).
* */
.directive('validatableWith', validatableWith)
/*
* retrieves a template by its name which is the same as model's type and renders it,
* recursive <typed-field></..>-s are possible.
* */
.directive('typedField', typedField);
typedField.$inject = ['$compile', 'merlin.templates'];
function editable() {
return {
restrict: 'E',
templateUrl: '/static/merlin/templates/editable.html',
require: 'ngModel',
scope: true,
link: function(scope, element, attrs, ngModelCtrl) {
var hiddenSpan = element.find('span.width-detector');
var input = element.find('input');
var maxWidth = 400;
function adjustWidth() {
var width;
hiddenSpan.html(scope.editableValue);
width = hiddenSpan.width();
input.width(width <= maxWidth ? width : maxWidth);
}
function accept() {
ngModelCtrl.$setViewValue(scope.editableValue);
scope.isEdited = false; scope.isEdited = false;
scope.$watch('editableValue', function() {
adjustWidth();
});
input.on('keyup', function(e) {
if ( e.keyCode == 13 ) {
accept();
scope.$apply();
} else if (e.keyCode == 27 ) {
reject();
scope.$apply();
}
});
ngModelCtrl.$render = function() {
if ( !ngModelCtrl.$viewValue ) {
ngModelCtrl.$viewValue = ngModelCtrl.$modelValue;
}
scope.editableValue = ngModelCtrl.$viewValue;
adjustWidth();
};
scope.accept = accept;
scope.reject = reject;
} }
};
}) function reject() {
/* ngModelCtrl.$rollbackViewValue();
* this directive auto-sets the focus to an input field once it is shown. scope.isEdited = false;
* */ }
.directive('showFocus', function($timeout) {
return function(scope, element, attrs) { scope.isEdited = false;
scope.$watch(attrs.showFocus, function(newValue) {
$timeout(function() { // Unused variable created here due to rule 'ng_on_watch': 2
newValue && element.focus(); // (see https://github.com/Gillespie59/eslint-plugin-angular)
}); var editableValueWatcher = scope.$watch('editableValue', function() {
adjustWidth();
}); });
input.on('keyup', function(e) {
if ( e.keyCode == 13 ) {
accept();
scope.$apply();
} else if (e.keyCode == 27 ) {
reject();
scope.$apply();
}
});
ngModelCtrl.$render = function() {
if ( !ngModelCtrl.$viewValue ) {
ngModelCtrl.$viewValue = ngModelCtrl.$modelValue;
}
scope.editableValue = ngModelCtrl.$viewValue;
adjustWidth();
};
scope.accept = accept;
scope.reject = reject;
} }
}) };
/* }
* tells Merlin to render this element as a panel.
* */ function showFocus($timeout) {
.directive('panel', function($parse) { return function(scope, element, attrs) {
return { // Unused variable created here due to rule 'ng_on_watch': 2
restrict: 'E', // (see https://github.com/Gillespie59/eslint-plugin-angular)
templateUrl: '/static/merlin/templates/collapsible-panel.html', var showFocusWatcher = scope.$watch(attrs.showFocus, function(newValue) {
transclude: true, $timeout(function() {
scope: { if (newValue) {
panel: '=content' element.focus();
}, }
link: function(scope, element, attrs) { });
scope.removable = $parse(attrs.removable)(); });
scope.isCollapsed = false; };
}
function panel($parse) {
return {
restrict: 'E',
templateUrl: '/static/merlin/templates/collapsible-panel.html',
transclude: true,
scope: {
panel: '=content'
},
link: function(scope, element, attrs) {
scope.removable = $parse(attrs.removable)();
scope.isCollapsed = false;
}
};
}
function collapsibleGroup() {
return {
restrict: 'E',
templateUrl: '/static/merlin/templates/collapsible-group.html',
transclude: true,
scope: {
group: '=content',
onAdd: '&',
onRemove: '&'
},
link: function(scope, element, attrs) {
scope.isCollapsed = false;
if ( attrs.onAdd && attrs.additive !== 'false' ) {
scope.additive = true;
}
if ( attrs.onRemove && attrs.removable !== 'false' ) {
scope.removable = true;
} }
} }
}) };
/* }
* tells Merlin to render this element as a group with ability to collapse.
* */ function validatableWith($parse) {
.directive('collapsibleGroup', function() { return {
return { restrict: 'A',
restrict: 'E', require: 'ngModel',
templateUrl: '/static/merlin/templates/collapsible-group.html', link: function(scope, element, attrs, ctrl) {
transclude: true, var model;
scope: { if ( attrs.validatableWith ) {
group: '=content', model = $parse(attrs.validatableWith)(scope);
onAdd: '&', scope.error = '';
onRemove: '&' if (model.setValidatable) {
}, model.setValidatable(true);
link: function(scope, element, attrs) {
scope.isCollapsed = false;
if ( attrs.onAdd && attrs.additive !== 'false' ) {
scope.additive = true;
} }
if ( attrs.onRemove && attrs.removable !== 'false' ) { if (model.on) {
scope.removable = true; model.on('validation', function(result) {
} var isValid = (result == 'succeeded');
} var baseMessage = '';
}
})
/*
* sets up the DOM nodes related to validation of model being edited in this widget (and specifies the name of this model on scope).
* */
.directive('validatableWith', function($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
var model;
if ( attrs.validatableWith ) {
model = $parse(attrs.validatableWith)(scope);
scope.error = '';
model.setValidatable && model.setValidatable(true);
model.on && model.on('validation', function(result) {
var isValid = (result == 'succeeded'),
baseMessage = '';
// (FIXME): hack until Barricade supports validation of empty required entries // (FIXME): hack until Barricade supports validation of empty required entries
if ( !model.get() && model.isRequired() ) { if ( !model.get() && model.isRequired() ) {
isValid = false; isValid = false;
baseMessage = 'This field is required.' baseMessage = 'This field is required.';
} }
ctrl.$setValidity('barricade', isValid); ctrl.$setValidity('barricade', isValid);
scope.error = model.hasError() ? model.getError() : baseMessage; scope.error = model.hasError() ? model.getError() : baseMessage;
}); });
ctrl.$formatters.push(function(modelValue) {
return modelValue === undefined ?
( ctrl.$isEmpty(ctrl.$viewValue) ? undefined : ctrl.$viewValue ) :
modelValue;
});
} }
ctrl.$formatters.push(function(modelValue) {
return angular.isUndefined(modelValue) ?
( ctrl.$isEmpty(ctrl.$viewValue) ? undefined : ctrl.$viewValue ) :
modelValue;
});
} }
} }
}) };
/* }
* retrieves a template by its name which is the same as model's type and renders it, recursive <typed-field></..>-s are possible.
* */
.directive('typedField', ['$compile', 'merlin.templates',
function($compile, templates) {
return {
restrict: 'E',
scope: {
value: '=',
type: '@'
},
link: function(scope, element) {
templates.templateReady(scope.type).then(function(template) {
element.replaceWith($compile(template)(scope));
})
}
}
}])
function typedField($compile, templates) {
return {
restrict: 'E',
scope: {
value: '=',
type: '@'
},
link: function(scope, element) {
templates.templateReady(scope.type).then(function(template) {
element.replaceWith($compile(template)(scope));
});
}
};
}
})(); })();

View File

@ -2,304 +2,308 @@
(function() { (function() {
'use strict'; 'use strict';
angular.module('merlin') angular
.factory('merlin.field.models', .module('merlin')
['merlin.utils', 'merlin.panel.models', '$http', function(utils, panels, $http) { .factory('merlin.field.models', merlinFieldModels);
var wildcardMixin = Barricade.Blueprint.create(function() { merlinFieldModels.$inject = ['merlin.utils', 'merlin.panel.models', '$http'];
return this;
});
var viewChoicesMixin = Barricade.Blueprint.create(function() { function merlinFieldModels(utils, panels, $http) {
var self = this, var wildcardMixin = Barricade.Blueprint.create(function() {
dropDownLimit = this._dropDownLimit || 5, return this;
values, labels, items, isDropDown; });
function fillItems() { var viewChoicesMixin = Barricade.Blueprint.create(function() {
values = self.getEnumValues(); var self = this;
labels = self.getEnumLabels(); var dropDownLimit = this._dropDownLimit || 5;
items = {}; var values, labels, items, isDropDown;
values && values.forEach(function (value, index) { function fillItems() {
values = self.getEnumValues();
labels = self.getEnumLabels();
items = {};
if (values) {
values.forEach(function (value, index) {
items[value] = labels[index]; items[value] = labels[index];
}); });
} }
this.getLabel = function(value) {
if ( values === undefined ) {
fillItems();
}
return items[value];
};
this.getValues = function() {
if ( values === undefined ) {
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;
});
var modelMixin = Barricade.Blueprint.create(function(type) {
var isValid = true,
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.isAtomic = function() {
return ['number', 'string', 'text', 'choices'].indexOf(this.getType()) > -1;
};
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({ this.getLabel = function(value) {
create: function(json, parameters) { if ( angular.isUndefined(values) ) {
var self = Barricade.Primitive.create.call(this, json, parameters); fillItems();
return modelMixin.call(self, 'string');
} }
}, {'@type': String}); return items[value];
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);
};
self.getValues = function() {
return self.toArray();
};
self._getContents = function() {
return self.toArray();
};
meldGroup.call(self);
return self;
}
}, {'@type': Array});
var frozendictModel = Barricade.ImmutableObject.extend({
create: function(json, parameters) {
var self = Barricade.ImmutableObject.create.call(this, json, parameters);
self.getKeys().forEach(function(key) {
utils.enhanceItemWithID(self.get(key), key);
});
modelMixin.call(self, 'frozendict');
self.getValues = function() {
return self._data;
};
self._getContents = function() {
return self.getKeys().map(function(key) {
return self.get(key);
})
};
meldGroup.call(self);
return self;
}
}, {'@type': Object});
var dictionaryModel = Barricade.MutableObject.extend({
create: function(json, parameters) {
var self = Barricade.MutableObject.create.call(this, json, parameters),
_items = [],
_elClass = self._elementClass,
baseKey = utils.getMeta(_elClass, 'baseKey') || 'key',
baseName = utils.getMeta(_elClass, 'baseName') || utils.makeTitle(baseKey);
modelMixin.call(self, 'dictionary');
function makeCacheWrapper(container, key) {
var value = container.getByID(key);
value.keyValue = function () {
if ( arguments.length ) {
value.setID(arguments[0]);
} else {
return value.getID();
}
};
return value;
}
self.add = function(newID) {
var regexp = new RegExp('(' + baseKey + ')([0-9]+)'),
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}));
_items.push(makeCacheWrapper(self, newID));
};
self.getValues = function() {
if ( !_items.length ) {
_items = self.toArray().map(function(value) {
return makeCacheWrapper(self, value.getID());
});
}
return _items;
};
self.empty = function() {
for ( var i = this._data.length; i > 0; i-- ) {
self.remove(i-1);
}
_items = [];
};
self.resetKeys = function(keys) {
self.empty();
keys.forEach(function(key) {
self.push(undefined, {id: key});
});
};
self._getContents = function() {
return self.toArray();
};
self.removeItem = function(key) {
var pos = self.getPosByID(key);
self.remove(self.getPosByID(key));
_items.splice(pos, 1);
};
meldGroup.call(self);
// initialize cache with starting values
self.getValues();
return self;
}
}, {'@type': Object});
var linkedCollectionModel = stringModel.extend({
create: function(json, parameters) {
var self = stringModel.create.call(this, json, parameters),
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
}; };
}])
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;
});
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.isAtomic = function() {
return ['number', 'string', 'text', 'choices'].indexOf(this.getType()) > -1;
};
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);
};
self.getValues = function() {
return self.toArray();
};
self._getContents = function() {
return self.toArray();
};
meldGroup.call(self);
return self;
}
}, {'@type': Array});
var frozendictModel = Barricade.ImmutableObject.extend({
create: function(json, parameters) {
var self = Barricade.ImmutableObject.create.call(this, json, parameters);
self.getKeys().forEach(function(key) {
utils.enhanceItemWithID(self.get(key), key);
});
modelMixin.call(self, 'frozendict');
self.getValues = function() {
return self._data;
};
self._getContents = function() {
return self.getKeys().map(function(key) {
return self.get(key);
});
};
meldGroup.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 _items = [];
var _elClass = self._elementClass;
var baseKey = utils.getMeta(_elClass, 'baseKey') || 'key';
var baseName = utils.getMeta(_elClass, 'baseName') || utils.makeTitle(baseKey);
modelMixin.call(self, 'dictionary');
function makeCacheWrapper(container, key) {
var value = container.getByID(key);
value.keyValue = function () {
if ( arguments.length ) {
value.setID(arguments[0]);
} else {
return value.getID();
}
};
return value;
}
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}));
_items.push(makeCacheWrapper(self, newID));
};
self.getValues = function() {
if ( !_items.length ) {
_items = self.toArray().map(function(value) {
return makeCacheWrapper(self, value.getID());
});
}
return _items;
};
self.empty = function() {
for ( var i = this._data.length; i > 0; i-- ) {
self.remove(i - 1);
}
_items = [];
};
self.resetKeys = function(keys) {
self.empty();
keys.forEach(function(key) {
self.push(undefined, {id: key});
});
};
self._getContents = function() {
return self.toArray();
};
self.removeItem = function(key) {
var pos = self.getPosByID(key);
self.remove(self.getPosByID(key));
_items.splice(pos, 1);
};
meldGroup.call(self);
// initialize cache with starting values
self.getValues();
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
};
}
})(); })();

View File

@ -14,143 +14,150 @@
under the License. under the License.
*/ */
(function() { (function() {
angular.module('merlin') angular
.module('merlin')
.filter('extractPanels', extractPanels)
.filter('extractRows', extractRows)
.filter('extractItems', extractItems);
.filter('extractPanels', ['merlin.utils', function(utils) { extractPanels.$inject = ['merlin.utils'];
var panelProto = { extractRows.$inject = ['merlin.utils'];
create: function(itemsOrContainer, id) { extractItems.$inject = ['merlin.utils'];
if ( angular.isArray(itemsOrContainer) && !itemsOrContainer.length ) {
return null; function extractPanels(utils) {
} var panelProto = {
if ( angular.isArray(itemsOrContainer) ) { create: function(itemsOrContainer, id) {
this.items = itemsOrContainer; if ( angular.isArray(itemsOrContainer) && !itemsOrContainer.length ) {
this.id = itemsOrContainer.reduce(function(prevId, item) { return null;
return item.uid() + prevId; }
}, ''); if ( angular.isArray(itemsOrContainer) ) {
this.items = itemsOrContainer;
this.id = itemsOrContainer.reduce(function(prevId, item) {
return item.uid() + prevId;
}, '');
} else {
this._barricadeContainer = itemsOrContainer;
this._barricadeId = id;
var barricadeObj = itemsOrContainer.getByID(id);
this.id = barricadeObj.uid();
this.items = barricadeObj.getKeys().map(function(key) {
return utils.enhanceItemWithID(barricadeObj.get(key), key);
});
this.removable = true;
}
return this;
},
title: function() {
var newID;
if ( this._barricadeContainer ) {
if ( arguments.length ) {
newID = arguments[0];
this._barricadeContainer.getByID(this._barricadeId).setID(newID);
this._barricadeId = newID;
} else { } else {
this._barricadeContainer = itemsOrContainer; return this._barricadeId;
this._barricadeId = id;
var barricadeObj = itemsOrContainer.getByID(id);
this.id = barricadeObj.uid();
this.items = barricadeObj.getKeys().map(function(key) {
return utils.enhanceItemWithID(barricadeObj.get(key), key);
});
this.removable = true;
} }
return this;
},
title: function() {
var newID;
if ( this._barricadeContainer ) {
if ( arguments.length ) {
newID = arguments[0];
this._barricadeContainer.getByID(this._barricadeId).setID(newID);
this._barricadeId = newID;
} else {
return this._barricadeId;
}
}
},
remove: function() {
var container = this._barricadeContainer,
pos = container.getPosByID(this._barricadeId);
container.remove(pos);
}
};
function isPanelsRoot(item) {
try {
// check for 'actions' and 'workflows' containers
return item.instanceof(Barricade.MutableObject);
}
catch(err) {
return false;
} }
},
remove: function() {
var container = this._barricadeContainer;
var pos = container.getPosByID(this._barricadeId);
container.remove(pos);
} }
};
function extractPanelsRoot(items) { function isPanelsRoot(item) {
return isPanelsRoot(items[0]) ? items[0] : null; try {
// check for 'actions' and 'workflows' containers
return item.instanceof(Barricade.MutableObject);
} }
catch(err) {
return false;
}
}
return _.memoize(function(container) { function extractPanelsRoot(items) {
var items = container._getContents(), return isPanelsRoot(items[0]) ? items[0] : null;
panels = []; }
utils.groupByMetaKey(items, 'panelIndex').forEach(function(items) {
var panelsRoot = extractPanelsRoot(items);
if ( panelsRoot ) {
panelsRoot.getIDs().forEach(function(id) {
panels.push(Object.create(panelProto).create(panelsRoot, id));
});
} else {
panels.push(Object.create(panelProto).create(items));
}
});
return panels.condense();
}, function(container) {
var hash = '';
container.getKeys().map(function(key) {
var item = container.get(key);
if ( isPanelsRoot(item) ) {
item.getIDs().forEach(function(id) {
hash += item.getByID(id).uid();
});
} else {
hash += item.uid();
}
});
return hash;
});
}])
.filter('extractRows', ['merlin.utils', function(utils) { return _.memoize(function(container) {
function getItems(panelOrContainer) { var items = container._getContents();
if ( panelOrContainer.items ) { var panels = [];
return panelOrContainer.items; utils.groupByMetaKey(items, 'panelIndex').forEach(function(items) {
} else if ( panelOrContainer.getKeys ) { var panelsRoot = extractPanelsRoot(items);
return panelOrContainer.getKeys().map(function(key) { if ( panelsRoot ) {
return panelOrContainer.get(key); panelsRoot.getIDs().forEach(function(id) {
panels.push(Object.create(panelProto).create(panelsRoot, id));
}); });
} else { } else {
return panelOrContainer.getIDs().map(function(id) { panels.push(Object.create(panelProto).create(items));
return panelOrContainer.getByID(id);
});
} }
});
return utils.condense(panels);
}, function(container) {
var hash = '';
container.getKeys().map(function(key) {
var item = container.get(key);
if ( isPanelsRoot(item) ) {
item.getIDs().forEach(function(id) {
hash += item.getByID(id).uid();
});
} else {
hash += item.uid();
}
});
return hash;
});
}
function extractRows(utils) {
function getItems(panelOrContainer) {
if ( panelOrContainer.items ) {
return panelOrContainer.items;
} else if ( panelOrContainer.getKeys ) {
return panelOrContainer.getKeys().map(function(key) {
return panelOrContainer.get(key);
});
} else {
return panelOrContainer.getIDs().map(function(id) {
return panelOrContainer.getByID(id);
});
} }
}
return _.memoize(function(panel) { return _.memoize(function(panel) {
var rowProto = { var rowProto = {
create: function(items) { create: function(items) {
this.id = items[0].uid(); this.id = items[0].uid();
this.index = items.row; this.index = items.row;
this.items = items.slice(); this.items = items.slice();
return this; return this;
} }
}; };
return utils.groupByMetaKey(getItems(panel), 'row').map(function(items) { return utils.groupByMetaKey(getItems(panel), 'row').map(function(items) {
return Object.create(rowProto).create(items); return Object.create(rowProto).create(items);
}); });
}, function(panel) { }, function(panel) {
var hash = ''; var hash = '';
getItems(panel).forEach(function(item) { getItems(panel).forEach(function(item) {
hash += item.uid(); hash += item.uid();
}); });
return hash; return hash;
}) });
}]) }
.filter('extractItems', ['merlin.utils', function(utils) {
return _.memoize(function(row) {
return row.items.sort(function(item1, item2) {
return utils.getMeta(item1, 'index') - utils.getMeta(item2, 'index');
});
}, function(row) {
var hash = '';
row.items.forEach(function(item) {
hash += item.uid();
});
return hash;
})
}])
function extractItems(utils) {
return _.memoize(function(row) {
return row.items.sort(function(item1, item2) {
return utils.getMeta(item1, 'index') - utils.getMeta(item2, 'index');
});
}, function(row) {
var hash = '';
row.items.forEach(function(item) {
hash += item.uid();
});
return hash;
});
}
})(); })();

View File

@ -4,20 +4,33 @@
(function() { (function() {
'use strict'; 'use strict';
angular.module('merlin', ['ui.bootstrap']) angular
.config(function($interpolateProvider) { .module('merlin', ['ui.bootstrap'])
// Replacing the default angular symbol .config(interpolateProvider)
// allow us to mix angular with django templates
$interpolateProvider.startSymbol('{$');
$interpolateProvider.endSymbol('$}');
})
// move these 2 values out of run section to change them in unit-tests // move these 2 values out of run section to change them in unit-tests
.value('fieldTemplatesUrl', '/static/merlin/templates/fields/') .value('fieldTemplatesUrl', '/static/merlin/templates/fields/')
.value('fieldTemplates', ['dictionary', 'frozendict', 'list', // The false posititive on array constant here we're working around is caused
'string', 'text', 'group', 'number', 'choices']) // by https://github.com/Gillespie59/eslint-plugin-angular/issues/99
.run(['merlin.templates', 'fieldTemplatesUrl', 'fieldTemplates', .value('fieldTemplates', fieldTemplates())
function(templates, rootUrl, fieldList) { .run(runTemplates);
templates.prefetch(rootUrl, fieldList);
}])
})(); runTemplates.$inject = ['merlin.templates', 'fieldTemplatesUrl', 'fieldTemplates'];
function fieldTemplates() {
return [
'dictionary', 'frozendict', 'list',
'string', 'text', 'group', 'number', 'choices'
];
}
function runTemplates(templates, rootUrl, fieldList) {
templates.prefetch(rootUrl, fieldList);
}
function interpolateProvider($interpolateProvider) {
// Replacing the default angular symbol
// allow us to mix angular with django templates
$interpolateProvider.startSymbol('{$');
$interpolateProvider.endSymbol('$}');
}
})();

View File

@ -4,46 +4,49 @@
(function() { (function() {
'use strict'; 'use strict';
angular.module('merlin') angular
.factory('merlin.panel.models', ['merlin.utils', function(utils) { .module('merlin')
.factory('merlin.panel.models', merlinPanelModels);
var groupMixin = Barricade.Blueprint.create(function() { merlinPanelModels.$inject = ['merlin.utils'];
var self = this,
additive = utils.getMeta(self, 'additive'),
removable = utils.getMeta(self, 'removable');
if ( additive === undefined ) { function merlinPanelModels(utils) {
additive = true; var groupMixin = Barricade.Blueprint.create(function() {
} var self = this;
self.isAdditive = function() { var additive = utils.getMeta(self, 'additive');
return additive; var removable = utils.getMeta(self, 'removable');
};
if ( removable === undefined ) { if ( angular.isUndefined(additive) ) {
removable = false; additive = true;
}
self.isRemovable = function() {
return removable;
};
if ( removable ) { // conditionally override common .title()
self.title = function() {
if ( arguments.length ) {
self.setID(arguments[0]);
} else {
return self.getID();
}
}
}
self.setType('group');
return self;
});
return {
groupmixin: groupMixin
} }
}]) self.isAdditive = function() {
return additive;
};
})(); if ( angular.isUndefined(removable) ) {
removable = false;
}
self.isRemovable = function() {
return removable;
};
if ( removable ) { // conditionally override common .title()
self.title = function() {
if ( arguments.length ) {
self.setID(arguments[0]);
} else {
return self.getID();
}
};
}
self.setType('group');
return self;
});
return {
groupmixin: groupMixin
};
}
})();

View File

@ -1,37 +1,41 @@
(function() { (function() {
angular.module('merlin') angular
.factory('merlin.templates', [ .module('merlin')
'$http', '$q', function($http, $q) { .factory('merlin.templates', merlinTemplates);
var promises = {};
function makeEmptyPromise() { merlinTemplates.$inject = ['$http', '$q'];
var deferred = $q.defer();
deferred.reject();
return deferred.promise;
}
function prefetch(baseUrl, fields) { function merlinTemplates($http, $q) {
if ( !angular.isArray(fields) ) { var promises = {};
fields = [fields];
}
fields.forEach(function(field) {
var deferred = $q.defer();
$http.get(baseUrl + field + '.html').success(function(templateContent) {
deferred.resolve(templateContent);
}).error(function(data) {
deferred.reject(data);
});
promises[field] = deferred.promise;
});
}
function templateReady(field) { function makeEmptyPromise() {
return promises[field] || makeEmptyPromise(); var deferred = $q.defer();
} deferred.reject();
return deferred.promise;
}
return { function prefetch(baseUrl, fields) {
prefetch: prefetch, if ( !angular.isArray(fields) ) {
templateReady: templateReady fields = [fields];
}; }
}]) fields.forEach(function(field) {
})(); var deferred = $q.defer();
$http.get(baseUrl + field + '.html').success(function(templateContent) {
deferred.resolve(templateContent);
}).error(function(data) {
deferred.reject(data);
});
promises[field] = deferred.promise;
});
}
function templateReady(field) {
return promises[field] || makeEmptyPromise();
}
return {
prefetch: prefetch,
templateReady: templateReady
};
}
})();

View File

@ -1,109 +1,115 @@
/** /**
* Created by tsufiev on 2/24/15. * Created by tsufiev on 2/24/15.
*/ */
(function() { (function() {
'use strict'; 'use strict';
angular.module('merlin') angular
.factory('merlin.utils', function() { .module('merlin')
Array.prototype.condense = function() { .factory('merlin.utils', merlinUtils);
return this.filter(function(el) {
return el !== undefined && el != null;
});
};
var _id_counter = 0; function merlinUtils() {
function condense(array) {
return array.filter(function(el) {
return angular.isDefined(el) && el !== null;
});
}
function getNewId() { var idCounter = 0;
_id_counter++;
return 'id-' + _id_counter;
}
function groupByMetaKey(sequence, metaKey, insertAtBeginning) { function getNewId() {
var newSequence = [], defaultBucket = [], idCounter++;
index; return 'id-' + idCounter;
sequence.forEach(function(item) { }
index = getMeta(item, metaKey);
if ( index !== undefined ) { function groupByMetaKey(sequence, metaKey, insertAtBeginning) {
if ( !newSequence[index] ) { var newSequence = [];
newSequence[index] = []; var defaultBucket = [];
newSequence[index][metaKey] = index; var index;
} sequence.forEach(function(item) {
newSequence[index].push(item); index = getMeta(item, metaKey);
} else { if ( angular.isDefined(index) ) {
defaultBucket.push(item); if ( !newSequence[index] ) {
newSequence[index] = [];
newSequence[index][metaKey] = index;
} }
}); newSequence[index].push(item);
newSequence = newSequence.condense();
// insert default bucket at the beginning/end of sequence
if ( defaultBucket.length ) {
if ( insertAtBeginning ) {
newSequence.splice(0, 0, defaultBucket);
} else {
newSequence.push(defaultBucket);
}
}
return newSequence;
}
function getMeta(item, key) {
if ( item ) {
var meta = item._schema['@meta'];
return meta && meta[key];
}
}
function makeTitle(str) {
if ( !str ) {
return '';
}
var firstLetter = str.substr(0, 1).toUpperCase();
return firstLetter + str.substr(1);
}
function getNextIDSuffix(container, regexp) {
var max = Math.max.apply(Math, container.getIDs().map(function(id) {
var match = regexp.exec(id);
return match && +match[2];
}));
return max > 0 ? max + 1 : 1;
}
function enhanceItemWithID(item, id) {
item.setID(id);
return item;
}
function pop(obj, key) {
if ( obj.hasOwnProperty(key) ) {
var value = obj[key];
delete obj[key];
return value;
} else { } else {
return undefined; defaultBucket.push(item);
}
});
newSequence = condense(newSequence);
// insert default bucket at the beginning/end of sequence
if ( defaultBucket.length ) {
if ( insertAtBeginning ) {
newSequence.splice(0, 0, defaultBucket);
} else {
newSequence.push(defaultBucket);
} }
} }
return newSequence;
}
function extend(proto, extension) { function getMeta(item, key) {
var newObj; if ( item ) {
proto = (proto !== undefined ? proto : null); var meta = item._schema['@meta'];
newObj = Object.create(proto); return meta && meta[key];
Object.keys(extension).forEach(function(key) {
newObj[key] = extension[key];
});
return newObj;
} }
}
return { function makeTitle(str) {
getMeta: getMeta, if ( !str ) {
getNewId: getNewId, return '';
groupByMetaKey: groupByMetaKey,
makeTitle: makeTitle,
getNextIDSuffix: getNextIDSuffix,
enhanceItemWithID: enhanceItemWithID,
extend: extend,
pop: pop
} }
}) var firstLetter = str.substr(0, 1).toUpperCase();
return firstLetter + str.substr(1);
}
function getNextIDSuffix(container, regexp) {
var max = Math.max.apply(Math, container.getIDs().map(function(id) {
var match = regexp.exec(id);
return match && +match[2];
}));
return max > 0 ? max + 1 : 1;
}
function enhanceItemWithID(item, id) {
item.setID(id);
return item;
}
function pop(obj, key) {
if ( obj.hasOwnProperty(key) ) {
var value = obj[key];
delete obj[key];
return value;
} else {
return undefined;
}
}
function extend(proto, extension) {
var newObj;
proto = (angular.isDefined(proto) ? proto : null);
newObj = Object.create(proto);
Object.keys(extension).forEach(function(key) {
newObj[key] = extension[key];
});
return newObj;
}
return {
getMeta: getMeta,
getNewId: getNewId,
groupByMetaKey: groupByMetaKey,
makeTitle: makeTitle,
getNextIDSuffix: getNextIDSuffix,
enhanceItemWithID: enhanceItemWithID,
extend: extend,
pop: pop,
condense: condense
};
}
})(); })();

View File

@ -16,15 +16,10 @@ describe('merlin.utils', function() {
}); });
}); });
describe('condense Array method', function() { describe('condense function', function() {
it('Array prototype should have condense()', function() {
var array = [];
expect(array.condense).toBeDefined();
});
it('condense() should throw away undefined and null values', function() { it('condense() should throw away undefined and null values', function() {
var array = [1, 0, 15, undefined, 7, null, null, 8]; var array = [1, 0, 15, undefined, 7, null, null, 8];
expect(array.condense()).toEqual([1, 0, 15, 7, 8]); expect(utils.condense(array)).toEqual([1, 0, 15, 7, 8]);
}); });
}); });