Take data for Angular.js from Barricade.js object

Along with this perform major code cleanup and change directory
structure.

Change-Id: I1736ded46ab5b9b635acce7e938a803001884393
This commit is contained in:
Timur Sufiev 2015-01-29 21:08:47 +03:00
parent 389af84bb4
commit fc14e3c993
39 changed files with 2748 additions and 1004 deletions

View File

@ -1,17 +0,0 @@
<div class="panel panel-default merlin-panel">
<div ng-if="title">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-target="#elem-{$ $id $}" href="#">{$ title $}</a>
<a href="#" ng-click="onRemove()"><i ng-show="removable" class="fa fa-times-circle pull-right"></i></a></h4>
</div>
<div id="elem-{$ $id $}" class="panel-collapse collapse in">
<div class="panel-body" ng-transclude>
</div>
</div>
</div>
<div ng-if="!title">
<div class="panel-body" ng-transclude>
</div>
</div>
</div>

View File

@ -1,15 +0,0 @@
<collapsible-group title="{$ spec.title || makeTitle(spec.name) $}" on-add="add(item[spec.name], '')">
<div class="three-columns" ng-repeat="(key, value) in item[spec.name] track by key">
<div class="left-column">
<div class="form-group">
<label for="elem-{$ $id $}.{$ key $}">
<editable value="key" label="New Name"></editable>
</label>
<div class="input-group">
<input id="elem-{$ $id $}.{$ key $}" type="text" class="form-control" ng-model="value">
<i class="fa fa-minus-circle input-group-addon"></i>
</div>
</div>
</div>
</div>
</collapsible-group>

View File

@ -1,10 +0,0 @@
<collapsible-group title="{$ spec.title || makeTitle(spec.name) $}">
<div class="three-columns">
<div class="left-column">
<div class="form-group" ng-repeat="(key, item) in item[spec.name] track by key">
<label for="elem-{$ $id $}.{$ key $}">{$ item.title || makeTitle(item.name) $}</label>
<input type="text" class="form-control" id="elem-{$ $id $}.{$ key $}" ng-model="item.value">
</div>
</div>
</div>
</collapsible-group>

View File

@ -1,11 +0,0 @@
<collapsible-group title="{$ spec.title || makeTitle(spec.name) $}" on-add="add(item[spec.name])" additive="{$ spec.additive $}">
<div ng-repeat="specs in spec.value | groupBy: 'row' | toArray: true | orderBy: '$key' track by specs.$key">
<div ng-class="{'three-columns': specs[0].row !== undefined }">
<div ng-repeat="spec in specs track by spec.name"
ng-class="{'right-column': $odd && isAtomic(spec.type), 'left-column': $even && isAtomic(spec.type)}">
<typed-field></typed-field>
<div class="clearfix" ng-show="$odd"></div>
</div>
</div>
</div>
</collapsible-group>

View File

@ -1,4 +0,0 @@
<div class="form-group">
<label for="elem-{$ $id $}">{$ spec.title || makeTitle(spec.name) $}</label>
<input type="text" class="form-control" id="elem-{$ $id $}" ng-model="item[spec.name]">
</div>

View File

@ -1,4 +0,0 @@
<div class="form-group">
<label for="elem-{$ $id $}">{$ spec.title || makeTitle(spec.name) $}</label>
<textarea class="form-control" id="elem-{$ $id $}" ng-model="item[spec.name]"></textarea>
</div>

View File

@ -0,0 +1,36 @@
/**
* Created by tsufiev on 12/29/14.
*/
(function() {
angular.module('hz')
.directive('yaqlFieldCombined', function() {
return {
restrict: 'E',
templateUrl: '/static/mistral/templates/yaql-field-combined.html',
scope: {
yaqlExpression: '@',
value: '@'
},
link: function(scope, element) {
angular.element(element).find('span.yaql-condition')
.on('click', function() {
var $elt = $(this),
$inputColumn = $elt.closest('.three-columns').children(':first-child'),
$input;
$elt.hide();
$input = $inputColumn.show().find('textarea');
$input.focus().on('blur', function() {
$inputColumn.hide();
$elt.toggleClass('fa-lock', $input.val() !== '');
$elt.toggleClass('fa-unlock', $input.val() === '');
$elt.show();
});
});
}
}
})
})();

View File

@ -0,0 +1,17 @@
/**
* Created by tsufiev on 2/24/15.
*/
(function() {
angular.module('hz')
.run(function($http, $templateCache) {
var fields = ['varlist', 'yaqllist'];
fields.forEach(function(field) {
var base = '/static/mistral/templates/fields/';
$http.get(base + field + '.html').success(function(templateContent) {
$templateCache.put(field, templateContent);
});
})
})
})();

View File

@ -0,0 +1,47 @@
/**
* Created by tsufiev on 2/24/15.
*/
(function() {
angular.module('hz')
.controller('workbookCtrl',
['$scope', 'mistral.workbook.models', function($scope, models) {
var workbook = models.Workbook.create();
$scope.workbook = workbook;
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 getWorkbookNextIDSuffix(base) {
var containerName = base + 's',
regexp = /(workflow|action)([0-9]+)/,
container = workbook.get(containerName);
if ( !container ) {
throw 'Base should be either "action" or "workflow"!';
}
return getNextIDSuffix(container, regexp);
}
var baseActionId = 'action', baseWorkflowId = 'workflow';
$scope.addAction = function() {
var nextSuffix = getWorkbookNextIDSuffix(baseActionId),
newID = baseActionId + nextSuffix;
workbook.get('actions').push({name: 'Action ' + nextSuffix}, {id: newID});
workbook.addPanel(workbook.get('actions'), newID, workbook.get('actions').length());
};
$scope.addWorkflow = function() {
var nextSuffix = getWorkbookNextIDSuffix(baseWorkflowId),
newID = baseWorkflowId + nextSuffix;
workbook.get('workflows').push({name: 'Workflow ' + nextSuffix}, {id: newID});
workbook.addPanel(workbook.get('workflows'), newID);
};
}])
})();

View File

@ -0,0 +1,440 @@
/**
* Created by tsufiev on 2/24/15.
*/
(function(){
angular.module('hz')
.factory('mistral.workbook.models',
['merlin.field.models', 'merlin.panel.models', function(fields, panel) {
var models = {};
var varlistValueFactory = function(json, parameters) {
var type = Barricade.getType(json);
if ( json === undefined || type === String ) {
return fields.string.create(json, parameters);
} else if ( type === Array ) {
return fields.list.extend({}, {
'*': {'@class': fields.string}
}).create(json, parameters);
} else if ( type === Object ) {
return fields.dictionary.extend({}, {
'?': {'@class': fields.string}
}).create(json, parameters);
}
};
models.varlist = fields.list.extend({
create: function(json, parameters) {
var self = fields.list.create.call(this, json, parameters);
self.setType('varlist');
self.on('childChange', function(child, op) {
if ( op == 'empty' ) {
self.each(function(index, item) {
if ( child === item ) {
self.remove(index);
}
})
}
});
return self;
}
}, {
'*': {
'@class': fields.frozendict.extend({
create: function(json, parameters) {
var self = fields.frozendict.create.call(this, json, parameters);
self.on('childChange', function(child) {
if ( child.instanceof(Barricade.Enumerated) ) { // type change
var value = self.get('value');
switch ( child.get() ) {
case 'string':
self.set('value', varlistValueFactory(''));
break;
case 'list':
self.set('value', varlistValueFactory(['']));
break;
case 'dictionary':
self.set('value', varlistValueFactory({'key1': ''}));
break;
}
} else if ( child.instanceof(Barricade.Arraylike) && !child.length() ) {
self.emit('change', 'empty');
}
});
return self;
}
}, {
'type': {
'@class': fields.string.extend({}, {
'@enum': ['string', 'list', 'dictionary'],
'@default': 'string'
})
},
'value': {
'@class': fields.wildcard,
'@factory': varlistValueFactory
}
})
}
});
models.Action = fields.frozendict.extend({}, {
'name': {
'@class': fields.string.extend({}, {
'@meta': {
'index': 0,
'row': 0
}
})
},
'base': {
'@class': fields.string.extend({}, {
'@meta': {
'index': 1,
'row': 0
}
})
},
'baseInput': {
'@class': fields.frozendict.extend({}, {
'@required': false,
'@meta': {
'index': 2,
'title': 'Base Input'
},
'?': {'@class': fields.string}
})
},
'input': {
'@class': fields.list.extend({}, {
'@meta': {
'index': 3
},
'*': {'@class': fields.string}
})
},
'output': {
'@class': models.varlist.extend({}, {
'@meta': {
'index': 4
}
})
}
});
models.Task = fields.frozendict.extend({}, {
'@meta': {
'baseKey': 'task',
'baseName': 'Task ',
'group': true,
'additive': false
},
'name': {
'@class': fields.string.extend({}, {
'@meta': {
'index': 0,
'row': 0
}
})
},
'type': {
'@class': fields.string.extend({}, {
'@enum': ['Action-based', 'Workflow-based'],
'@meta': {
'index': 1,
'row': 0
}
})
},
'action': {
'@class': fields.string.extend({}, {
'@meta': {
'index': 2,
'row': 1
}
})
},
'input': {
'@class': fields.dictionary.extend({}, {
'@meta': {
'index': 3
},
'?': {
'@class': fields.string
}
})
},
'publish': {
'@class': fields.dictionary.extend({}, {
'@meta': {
'index': 4
},
'?': {
'@class': fields.string
}
})
},
'onError': {
'@class': fields.list.extend({}, {
'@meta': {
'title': 'On error',
'index': 5
},
'*': {
'@class': fields.string
}
})
},
'onSuccess': {
'@class': fields.list.extend({}, {
'@meta': {
'title': 'On success',
'index': 6
},
'*': {
'@class': fields.string
}
})
},
'onComplete': {
'@class': fields.list.extend({}, {
'@meta': {
'title': 'On complete',
'index': 7
},
'*': {
'@class': fields.string
}
})
},
'policies': {
'@class': fields.frozendict.extend({}, {
'@meta': {
'index': 8
},
'@required': false,
'waitBefore': {
'@class': fields.number.extend({}, {
'@required': false,
'@meta': {
'index': 0,
'row': 0,
'title': 'Wait before'
}
})
},
'waitAfter': {
'@class': fields.number.extend({}, {
'@required': false,
'@meta': {
'index': 1,
'row': 0,
'title': 'Wait after'
}
})
},
'timeout': {
'@class': fields.number.extend({}, {
'@required': false,
'@meta': {
'index': 2,
'row': 1
}
})
},
'retryCount': {
'@class': fields.number.extend({}, {
'@required': false,
'@meta': {
'index': 3,
'row': 2,
'title': 'Retry count'
}
})
},
'retryDelay': {
'@class': fields.number.extend({}, {
'@required': false,
'@meta': {
'index': 4,
'row': 2,
'title': 'Retry delay'
}
})
},
'retryBreakOn': {
'@class': fields.number.extend({}, {
'@required': false,
'@meta': {
'index': 5,
'row': 3,
'title': 'Retry break on'
}
})
}
})
}
});
models.Workflow = fields.frozendict.extend({}, {
'name': {
'@class': fields.string.extend({}, {
'@meta': {
'index': 0,
'row': 0
}
})
},
'type': {
'@class': fields.string.extend({}, {
'@enum': ['reverse', 'direct'],
'@default': 'direct',
'@meta': {
'index': 1,
'row': 0
}
})
},
'input': {
'@class': fields.list.extend({}, {
'@required': false,
'@meta': {
'index': 2
},
'*': {
'@class': fields.string
}
})
},
'output': {
'@class': fields.list.extend({}, {
'@required': false,
'@meta': {
'index': 3
},
'*': {
'@class': fields.string
}
})
},
'taskDefaults': {
'@class': fields.frozendict.extend({}, {
'@required': false,
'@meta': {
'index': 4,
'group': true,
'additive': false
},
'onError': {
'@class': fields.list.extend({}, {
'@meta': {
'title': 'On error',
'index': 0
},
'*': {
'@class': fields.string
}
})
},
'onSuccess': {
'@class': fields.list.extend({}, {
'@meta': {
'title': 'On success',
'index': 1
},
'*': {
'@class': fields.string
}
})
},
'onComplete': {
'@class': fields.list.extend({}, {
'@meta': {
'title': 'On complete',
'index': 2
},
'*': {
'@class': fields.string
}
})
}
})
},
'tasks': {
'@class': fields.dictionary.extend({}, {
'@meta': {
'index': 5,
'group': true
},
'?': {
'@class': models.Task
}
})
}
});
models.Workbook = fields.frozendict.extend({
create: function(json, parameters) {
var self = fields.frozendict.create.call(this, json, parameters);
return panel.panelmixin.call(self);
}
}, {
'version': {
'@class': fields.number.extend({}, {
'@enum': [2],
'@meta': {
'index': 2,
'panelIndex': 0,
'row': 1
},
'@default': 2
})
},
'name': {
'@class': fields.string.extend({}, {
'@meta': {
'index': 0,
'panelIndex': 0,
'row': 0
}
})
},
'description': {
'@class': fields.text.extend({}, {
'@meta': {
'index': 1,
'panelIndex': 0,
'row': 0
},
'@required': false
})
},
'actions': {
'@class': fields.dictionary.extend({}, {
'@required': false,
'@meta': {
'index': 3,
'panelIndex': 1
},
'?': {
'@class': models.Action
}
})
},
'workflows': {
'@class': fields.dictionary.extend({}, {
'@meta': {
'index': 4,
'panelIndex': 2
},
'?': {
'@class': models.Workflow
}
})
}
});
return models;
}])
})();

View File

@ -14,7 +14,9 @@
angular.module('hz')
.controller('workbookCtrl', function($scope) {
.controller('workbookCtrl', function($scope, workbook, $filter) {
$scope.workbook = workbook;
$scope.defaults = {
'actions': {
name: 'Action1',
@ -280,6 +282,8 @@
}
};
$scope.makeTitle = function(str) {
if ( !str ) {
return '';
@ -293,7 +297,7 @@
};
$scope.isAtomic = function(type) {
return ['string', 'text'].indexOf(type) > -1;
return ['string', 'text', 'number'].indexOf(type) > -1;
};
$scope.remove = function(parent, item) {
@ -367,20 +371,6 @@
}
};
//$scope.getBaseInput = function() {
// return baseTypes[actionBase] || [];
//};
//
//$scope.updateBase = function(newBase) {
// actionBase = newBase;
// var values = [];
// angular.forEach($scope.getBaseInput(), function(item) {
// values.push(item.value || '');
// });
// $scope.item.baseInput.value = values;
//}
})
.controller('dictionaryCtrl', function($scope) {

View File

@ -0,0 +1,213 @@
/* Copyright (c) 2014 Mirantis, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
*/
(function() {
angular.module('hz')
.factory('workbook', function() {
var types = {
Mistral: {},
base: {},
OpenStack: {
// TODO: obtain list of predefined OpenStack actions from Mistral server-side
// for now a stubbed list of predefined actions suffices
actions: ['createInstance', 'terminateInstance']
},
getOpenStackActions: function() {
return this.OpenStack.actions.slice();
}
};
//types.base.AcceptsMixin = Barricade.Blueprint.create(function (acceptsList) {
// acceptsList = acceptsList || [];
//
// this.getLabels = function() {
// return acceptsList.map(function(item) {
// return item.label;
// })
// };
//
// this.getValue = function(label) {
// for ( var i = 0; i < acceptsList.length; i++ ) {
// if ( acceptsList[i].label === label ) {
// return acceptsList[i].value;
// }
// }
// return null;
// }
//});
//types.Mistral.Task = Barricade.create({
// '@class': types.Mistral.dictionary,
//
// 'name': {'@type': String},
// 'input': {
// '@type': Array,
// '*': {
// '@class': Barricade.Primitive.extend({
// 'name': 'Parameter'
// }, {
// '@type': String
// })
// }
// },
// 'publish': {
// '@type': String,
// '@required': false
// },
// 'policies': {
// '@class': types.Mistral.Policy,
// '@required': false
// }
//});
//
//types.Mistral.Tasks = Barricade.MutableObject.extend({
// create: function(json, parameters) {
// var self = Barricade.MutableObject.create.call(this);
//
// function getParentWorkflowType() {
// var container = self._container,
// workflow;
// while ( container ) {
// if ( container.instanceof(types.Mistral.Workflow) ) {
// workflow = container;
// break;
// }
// container = container._container;
// }
// return workflow && workflow.get('type').get();
// }
//
// var directSpecificData = {
// 'on-complete': {
// '@type': String,
// '@required': false
// },
// 'on-success': {
// '@type': String,
// '@required': false
// },
// 'on-error': {
// '@type': String,
// '@required': false
// }
// },
// reverseSpecificData = {
// 'requires': {
// '@type': Array,
// '*': {
// '@class': Barricade.Primitive.extend({
// 'name': 'Action'
// }, {
// '@type': String,
// '@enum': function() {
// var container = this._container,
// workflow, task;
// while ( container ) {
// if ( container.instanceof(types.Mistral.Task) ) {
// task = container;
// }
// if ( container.instanceof(types.Mistral.Workflow) ) {
// workflow = container;
// break;
// }
// container = container._container;
// }
// if ( workflow && task ) {
// return workflow.get('tasks').toArray().filter(function(taskItem) {
// return !(taskItem === task) && taskItem.get('name').get();
// }).map(function(taskItem) {
// return taskItem.get('name').get();
// });
// } else {
// return [];
// }
// }
// })
// }
// }
// };
//
// types.base.AcceptsMixin.call(self, [
// {
// label: 'Action-based',
// value: function() {
// var workflowType = getParentWorkflowType();
// if ( workflowType === 'direct' ) {
// return types.Mistral.ActionTask.extend({}, directSpecificData);
// } else if ( workflowType === 'reverse' ) {
// return types.Mistral.ActionTask.extend({}, reverseSpecificData);
// } else {
// return types.Mistral.ActionTask;
// }
// }
// }, {
// label: 'Workflow-based',
// value: function() {
// var workflowType = getParentWorkflowType();
// if ( workflowType === 'direct' ) {
// return types.Mistral.WorkflowTask.extend({}, directSpecificData);
// } else if ( workflowType === 'reverse' ) {
// return types.Mistral.WorkflowTask.extend({}, reverseSpecificData);
// } else {
// return types.Mistral.WorkflowTask;
// }
// }
// }
// ]);
// return self;
// }
//}, {
// '@type': Object,
// '?': {'@class': types.Mistral.Task}
//});
//
//types.Mistral.WorkflowTask = types.Mistral.Task.extend({},
// {
// 'workflow': {
// '@type': String,
// '@enum': function() {
// var workflows = workbook.get('workflows').toArray();
// return workflows.map(function(workflowItem) {
// return workflowItem.get('name').get();
// }).filter(function (name) {
// return name;
// });
// }
// }
// });
//
//types.Mistral.ActionTask = types.Mistral.Task.extend({},
// {
// 'action': {
// '@type': String,
// '@enum': function() {
// var predefinedActions = types.getOpenStackActions(),
// actions = workbook.get('actions').toArray();
// return predefinedActions.concat(actions.map(function(actionItem) {
// return actionItem.get('name').get();
// }).filter(function(name) {
// return name; }
// ));
// }
// }
// });
return types.Mistral.Workbook.create();
})
})();

View File

@ -301,16 +301,29 @@ types.Mistral.Workbook = Barricade.create({
'version': {
'@type': Number,
'@meta': {'groups': ['panel1']},
'@enum': function() { return [2]; },
'@meta': {
'index': 2,
'panelIndex': 0,
'row': 1
},
'@default': 2
},
'name': {
'@type': String,
'@meta': {'groups': ['panel1']}
'@meta': {
'index': 0,
'panelIndex': 0,
'row': 0
}
},
'description': {
'@type': String,
'@meta': {'groups': ['panel1']},
'@meta': {
'index': 1,
'panelIndex': 0,
'row': 0
},
'@required': false
},
'actions': {

View File

@ -17,9 +17,10 @@
.run(function($http, $templateCache) {
var fields = ['dictionary', 'frozendict', 'list', 'string',
'varlist', 'text', 'group', 'yaqllist'];
'varlist', 'text', 'group', 'yaqllist', 'number'
];
fields.forEach(function(field) {
var base = '/static/mistral/js/angular-templates/fields/';
var base = '/static/mistral/js/templates-templates/fields/';
$http.get(base + field + '.html').success(function(templateContent) {
$templateCache.put(field, templateContent);
});
@ -62,6 +63,45 @@
}
})
.filter('getPanels', function($filter) {
var orderBy = $filter('orderBy'),
groupBy = $filter('groupBy'),
toArray = $filter('toArray');
return function(container) {
var seq = groupBy(container, 'getMeta().panelIndex');
console.log('seq', seq);
var seq1 = toArray(seq, true);
console.log('seq1', seq1);
var seq2 = orderBy(seq1, '$key');
console.log('seq2', seq2);
// var seq = orderBy(
// toArray(, true),
// '$key');
return seq2;
}
})
function sign(x) {
if ( x > 0 ) {
return 1;
} else {
return x < 0 ? -1 : 0;
}
}
function comparePanelIndices(item1, item2) {
var index1 = item1.getMeta().panelIndex,
index2 = item2.getMeta().panelIndex;
if ( index1 === undefined || index2 === undefined ) {
return -1;
} else {
return sign(index1-index2);
}
}
})();

View File

@ -1,25 +1,28 @@
<collapsible-group title="{$ spec.title || makeTitle(spec.name) $}"
on-add="add(item[spec.name], {type: 'string', value: '', id: 'varlist'+item[spec.name].length})">
<div class="three-columns" ng-repeat="subItem in item[spec.name] track by subItem.id"
ng-class="{dictionary: subItem.type == 'dictionary', list: subItem.type == 'list'}">
<collapsible-group title="{$ title $}"
on-add="value.add()">
<div class="three-columns" ng-repeat="subItem in value.getValues() track by $index"
ng-class="subItem.get('type').get()">
<div class="left-column">
<div class="form-group">
<label for="elem-{$ $id $}.$index">Key Type</label>
<select id="elem-{$ $id $}.$index" class="form-control" ng-model="subItem.type">
<option ng-repeat="value in ['string', 'list', 'dictionary']"
value="{$ value $}" ng-selected="subItem.type == value">{$ makeTitle(value) $}</option>
<select id="elem-{$ $id $}.$index" class="form-control"
ng-model="subItem.get('type').value" ng-model-options="{getterSetter: true}">
<option ng-repeat="value in subItem.get('type').getEnumValues()"
value="{$ value $}"
ng-selected="subItem.get('type').get() == value">{$ value $}</option>
</select>
</div>
</div>
<div ng-switch="subItem.type">
<div ng-switch="subItem.get('type').value()">
<!-- draw string input -->
<div class="right-column" ng-switch-when="string">
<div class="form-group">
<label>&nbsp;</label>
<div class="input-group">
<input type="text" class="form-control" ng-model="subItem.value">
<input type="text" class="form-control"
ng-model="subItem.get('value').value" ng-model-options="{getterSetter: true}">
<span class="input-group-btn">
<button class="btn btn-default" ng-click="remove(item[spec.name], subItem)">
<button class="btn btn-default" ng-click="value.remove($index)">
<i class="fa fa-minus-circle"></i>
</button>
</span>
@ -28,8 +31,8 @@
</div>
<!-- END: draw string input -->
<!-- draw dictionary inputs -->
<div ng-switch-when="dictionary" ng-controller="dictionaryCtrl">
<div ng-repeat="(key, value) in subItem.value track by key">
<div ng-switch-when="dictionary">
<div ng-repeat="(key, value) in subItem.get('value').getValues() track by key">
<div ng-hide="$first" class="left-column"></div>
<div class="right-column">
<div class="form-group">
@ -37,9 +40,10 @@
<editable value="key" label="New Name"></editable>
</label>
<div class="input-group">
<input type="text" id="elem-{$ $id $}.{$ key $}" class="form-control" ng-model="value">
<input type="text" id="elem-{$ $id $}.{$ key $}" class="form-control" ng-model="value.value"
ng-model-options="{getterSetter: true}">
<span class="input-group-btn">
<button class="btn btn-default" ng-click="removeKey(subItem.value, key) || remove(item[spec.name], subItem)">
<button class="btn btn-default" ng-click="subItem.get('value').remove(key)">
<i class="fa fa-minus-circle"></i>
</button>
</span>
@ -48,7 +52,7 @@
</div>
<div ng-hide="$last" class="clearfix"></div>
<div ng-show="$last" class="add-btn button-column">
<button class="btn btn-default btn-sm pull-right" ng-click="addAutoKey(subItem.value)">
<button class="btn btn-default btn-sm pull-right" ng-click="subItem.get('value').add()">
<i class="fa fa-plus"></i>
</button>
</div>
@ -56,16 +60,17 @@
</div>
<!-- END: draw dictionary inputs -->
<!-- draw list inputs -->
<div ng-switch-when="list" ng-controller="listCtrl">
<div ng-repeat="value in subItem.value track by $index">
<div ng-switch-when="list">
<div ng-repeat="value in subItem.get('value').getValues() track by $index">
<div ng-hide="$first" class="left-column"></div>
<div class="right-column">
<div class="form-group">
<label ng-show="$first">&nbsp;</label>
<div class="input-group">
<input type="text" class="form-control" ng-model="value">
<input type="text" class="form-control" ng-model="value.value"
ng-model-options="{getterSetter: true}">
<span class="input-group-btn">
<button class="btn btn-default" ng-click="remove(subItem.value, value) || remove(item[spec.name], subItem)">
<button class="btn btn-default" ng-click="subItem.get('value').remove($index)">
<i class="fa fa-minus-circle"></i>
</button>
</span>
@ -74,7 +79,7 @@
</div>
<div ng-hide="$last" class="clearfix"></div>
<div ng-show="$last" class="add-btn button-column" ng-class="{'varlist-1st-row': !$index}">
<button class="btn btn-default btn-sm pull-right" ng-click="add(subItem.value, '')">
<button class="btn btn-default btn-sm pull-right" ng-click="subItem.get('value').add()">
<i class="fa fa-plus"></i>
</button>
</div>

View File

@ -11,9 +11,18 @@
{% block js %}
{% include "horizon/_scripts.html" %}
<script type="text/javascript" src="{{ STATIC_URL }}mistral/js/services.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}mistral/js/directives.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}mistral/js/controllers.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}merlin/js/lib/barricade.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}merlin/js/merlin.directives.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}merlin/js/merlin.field.models.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}merlin/js/merlin.init.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}merlin/js/merlin.panel.models.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}merlin/js/merlin.utils.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}mistral/js/mistral.directives.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}mistral/js/mistral.init.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}mistral/js/mistral.workbook.controllers.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}mistral/js/mistral.workbook.models.js"></script>
{% endblock %}
{% block css %}
@ -32,12 +41,12 @@
<div class="two-panels">
<div class="left-panel">
<div class="pull-left">
<h4><strong>Workbook1</strong></h4>
<h4><strong>{$ workbook.get('name') $}</strong></h4>
</div>
<div class="pull-right">
<div class="table-actions clearfix">
<button ng-click="add('actions')" class="btn btn-default btn-sm"><span class="fa fa-plus">Add Action</span></button>
<button class="btn btn-default btn-sm"><span class="fa fa-plus">Add Workflow</span></button>
<button ng-click="addAction()" class="btn btn-default btn-sm"><span class="fa fa-plus">Add Action</span></button>
<button ng-click="addWorkflow()" class="btn btn-default btn-sm"><span class="fa fa-plus">Add Workflow</span></button>
</div>
</div>
</div>
@ -51,33 +60,8 @@
<!-- Data panel start -->
<div class="two-panels">
<div class="left-panel">
<div ng-repeat="panelSpec in schema | prepareSchema | groupBy: 'panelIndex' | toArray:true | orderBy: '$key' | normalizePanels track by panelSpec.$key">
<panel ng-if="panelSpec.multiple" ng-repeat="item in data[panelSpec.name] track by item.id" title="{$ item.name $}" on-remove="remove('{$ panelSpec.name $}', item)">
<div ng-repeat="specs in panelSpec | groupBy: 'row' | toArray: true | orderBy: '$key' track by specs.$key">
<div ng-class="{'three-columns': specs[0].row !== undefined }">
<div ng-repeat="spec in specs track by spec.name"
ng-class="{'right-column': $even && isAtomic(spec.type), 'left-column': $odd && isAtomic(spec.type)}">
<typed-field></typed-field>
<div class="clearfix" ng-show="$even"></div>
</div>
</div>
</div>
</panel>
<panel ng-if="!panelSpec.multiple">
<!-- ugly duplication here -->
<div ng-repeat="specs in panelSpec | groupBy: 'row' | toArray: true | orderBy: '$key' track by specs.$key">
<div ng-class="{'two-columns': specs[0].row !== undefined }">
<div ng-repeat="spec in specs track by spec.name"
ng-class="{'right-column': $even && isAtomic(spec.type), 'left-column': $odd && isAtomic(spec.type)}">
<typed-field></typed-field>
<div class="clearfix" ng-show="$even"></div>
</div>
</div>
</div>
</panel>
</div>
<!-- panel with workflow -->
<div ng-controller="workflowsCtrl">
<div>
<panel title="Workflow1" removable="true">
<!-- 2 simple inputs in a single row -->
<div class="three-columns">
@ -274,6 +258,19 @@ name: Workbook1
description: </pre>
</div>
</div>
<panel ng-repeat="panel in workbook.getPanels() track by panel.id"
title="{$ panel.getTitle() $}" removable="{$ panel.removable $}"
on-remove="panel.remove(panel.id)">
<div ng-repeat="row in panel.getRows() track by row.id">
<div ng-class="{'two-columns': row.index !== undefined }">
<div ng-repeat="item in row.getItems() track by item.id"
ng-class="{'right-column': item.isAtomic() && $odd, 'left-column': item.isAtomic() && $even}">
<typed-field title="{$ item.getTitle() $}" value="item" type="{$ item.getType() $}"></typed-field>
<div class="clearfix" ng-if="$odd"></div>
</div>
</div>
</div>
</panel>
</div>
</div>
<!-- page footer -->

View File

@ -155,11 +155,11 @@ function isScope(obj) {
}
/**
* @ngdoc filter
* @name a8m.angular
* @name a8m.templates
* @kind function
*
* @description
* reference to angular function
* reference to templates function
*/
angular.module('a8m.angular', [])
@ -2022,7 +2022,7 @@ angular.module('a8m.filter-watcher', [])
/**
* @description
* for angular version that greater than v.1.3.0
* for templates version that greater than v.1.3.0
* if clear cache when the digest cycle end.
*/
function cleanStateless() {
@ -2075,7 +2075,7 @@ angular.module('a8m.filter-watcher', [])
var hashKey = getHashKey(filterName, args);
//store result in `$$cache` container
$$cache[hashKey] = result;
// for angular versions that less than 1.3
// for templates versions that less than 1.3
// add to `$destroy` listener, a cleaner callback
if(isScope(scope)) {
addListener(scope, hashKey);
@ -2114,7 +2114,7 @@ angular.module('a8m.filter-watcher', [])
/**
* @ngdoc module
* @name angular.filters
* @name templates.filters
* @description
* Bunch of useful filters for angularJS
*/

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,9 @@
/**
* Created by tsufiev on 12/29/14.
* Created by tsufiev on 2/24/15.
*/
(function() {
function disableClickDefaultBehaviour(element) {
angular.element(element).find('a[data-toggle="collapse"]')
element.find('a[data-toggle="collapse"]')
.on('click', function(e) {
e.preventDefault();
return true;
@ -16,7 +15,7 @@
.directive('editable', function() {
return {
restrict: 'E',
templateUrl: '/static/mistral/js/angular-templates/editable-popup.html',
templateUrl: '/static/merlin/templates/editable-popup.html',
scope: {
label: '@',
value: '='
@ -32,48 +31,18 @@
};
})
.directive('yaqlFieldCombined', function() {
.directive('panel', function($parse) {
return {
restrict: 'E',
templateUrl: '/static/mistral/js/angular-templates/yaql-field-combined.html',
scope: {
yaqlExpression: '@',
value: '@'
},
link: function(scope, element) {
angular.element(element).find('span.yaql-condition')
.on('click', function() {
var $elt = $(this),
$inputColumn = $elt.closest('.three-columns').children(':first-child'),
$input;
$elt.hide();
$input = $inputColumn.show().find('textarea');
$input.focus().on('blur', function() {
$inputColumn.hide();
$elt.toggleClass('fa-lock', $input.val() !== '');
$elt.toggleClass('fa-unlock', $input.val() === '');
$elt.show();
});
});
}
}
})
.directive('panel', function() {
return {
restrict: 'E',
templateUrl: '/static/mistral/js/angular-templates/collapsible-panel.html',
templateUrl: '/static/merlin/templates/collapsible-panel.html',
transclude: true,
scope: {
title: '@',
onRemove: '&'
},
link: function(scope, element, attrs) {
scope.removable = $parse(attrs.removable)();
disableClickDefaultBehaviour(element);
if ( attrs.onRemove ) {
scope.removable = true;
}
}
}
})
@ -81,7 +50,7 @@
.directive('collapsibleGroup', function() {
return {
restrict: 'E',
templateUrl: '/static/mistral/js/angular-templates/collapsible-group.html',
templateUrl: '/static/merlin/templates/collapsible-group.html',
transclude: true,
scope: {
title: '@',
@ -103,12 +72,16 @@
.directive('typedField', function($http, $templateCache, $compile) {
return {
restrict: 'E',
scope: true,
scope: {
title: '@',
value: '=',
type: '@'
},
link: function(scope, element) {
var template = $templateCache.get(scope.spec.type);
var template = $templateCache.get(scope.type);
element.replaceWith($compile(template)(scope));
}
}
})
})();
})();

View File

@ -0,0 +1,174 @@
(function() {
angular.module('hz')
.factory('merlin.field.models',
['merlin.utils', 'merlin.panel.models', function(utils, panels) {
var wildcardMixin = Barricade.Blueprint.create(function() {
return this;
});
var modelMixin = Barricade.Blueprint.create(function(type) {
this.value = function() {
if ( !arguments.length ) {
return this.get();
} else {
this.set(arguments[0]);
}
};
this.id = utils.getNewId();
this.getType = function() {
return type;
};
this.setType = function(_type) {
type = _type;
};
this.isAtomic = function() {
return ['number', 'string', 'text'].indexOf(this.getType()) > -1;
};
this.getTitle = 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);
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();
};
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);
})
};
panels.rowmixin.call(self);
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');
self.add = function() {
var newID = baseKey + utils.getNextIDSuffix(self, /(key)([0-9]+)/),
newValue;
if ( _elClass.instanceof(Barricade.ImmutableObject) ) {
if ( 'name' in _elClass._schema ) {
var nameNum = utils.getNextIDSuffix(self, new RegExp('(' + baseName + ')([0-9]+)'));
newValue = {name: baseName + nameNum};
} else {
newValue = {};
}
} else { // usually, it's either frozendict inside or string
newValue = '';
}
self.push(newValue, {id: newID});
_items[newID] = self.getByID(newID);
};
self.getValues = function() {
if ( !Object.keys(_items).length ) {
self.getIDs().forEach(function(id) {
_items[id] = self.getByID(id);
});
}
return _items;
};
self._getContents = function() {
return self.toArray();
};
self.remove = function(key) {
delete _items[key];
Barricade.MutableObject.remove.call(self, self.getPosByID(key));
};
meldGroup.call(self);
return self;
}
}, {'@type': Object});
return {
string: stringModel,
text: textModel,
number: numberModel,
list: listModel,
dictionary: dictionaryModel,
frozendict: frozendictModel,
wildcard: wildcardMixin // use for most general type-checks
};
}])
})();

View File

@ -0,0 +1,19 @@
/**
* Created by tsufiev on 2/24/15.
*/
(function() {
angular.module('hz')
.run(function($http, $templateCache) {
var fields = ['dictionary', 'frozendict', 'list', 'string',
'text', 'group', 'number'
];
fields.forEach(function(field) {
var base = '/static/merlin/templates/fields/';
$http.get(base + field + '.html').success(function(templateContent) {
$templateCache.put(field, templateContent);
});
})
})
})();

View File

@ -0,0 +1,152 @@
/**
* Created by tsufiev on 2/24/15.
*/
(function(){
angular.module('hz')
.factory('merlin.panel.models', ['merlin.utils', function(utils) {
var rowProto = {
create: function(items) {
this.id = utils.getNewId();
this.index = items.row;
items = items.slice();
this._items = items.sort(function(item1, item2) {
return utils.getMeta(item1, 'index') - utils.getMeta(item2, 'index');
});
return this;
},
getItems: function() {
return this._items;
}
};
var panelMixin = Barricade.Blueprint.create(function (schema) {
var self = this,
panelProto = {
create: function(itemsOrContainer, id) {
if ( angular.isArray(itemsOrContainer) && !itemsOrContainer.length ) {
return null;
}
this.id = utils.getNewId();
if ( angular.isArray(itemsOrContainer) ) {
this._items = itemsOrContainer;
} else {
this._barricadeContainer = itemsOrContainer;
this._barricadeId = id;
var barricadeObj = itemsOrContainer.getByID(id);
this._items = barricadeObj.getKeys().map(function(key) {
return utils.enhanceItemWithID(barricadeObj.get(key), key);
});
this.removable = true;
}
return this;
},
getTitle: function() {
if ( this._barricadeContainer ) {
return this._barricadeContainer.getByID(this._barricadeId).get('name');
}
},
getRows: function() {
if ( this._rows === undefined ) {
this._rows = utils.groupByMetaKey(this._items, 'row').map(function(items) {
return Object.create(rowProto).create(items);
});
}
return this._rows;
},
remove: function(id) {
for ( var i = 0; i < panels.length; i++ ) {
if ( panels[i].id === id ) {
var container = this._barricadeContainer;
container.remove.call(container, this._barricadeId);
panels.splice(i, 1);
break;
}
}
}
},
panels;
this.getPanels = function(filterKey) {
if ( panels === undefined ) {
panels = [];
var items = self._getContents();
utils.groupByMetaKey(items, 'panelIndex').forEach(function(items) {
// check for 'actions' and 'workflows' containers
if ( items[0].instanceof(Barricade.MutableObject) ) {
items[0].getIDs().forEach(function(id) {
panels.push(Object.create(panelProto).create(items[0], id));
});
} else {
panels.push(Object.create(panelProto).create(items));
}
});
panels = panels.condense();
}
if ( filterKey ) {
panels.filter(function(panel) {
return panel._barricadeId && panel._barricadeId.match(filterKey);
})
}
return panels;
};
this.addPanel = function(barricadeContainer, itemID, panelIndex) {
var panel = Object.create(panelProto).create(barricadeContainer, itemID);
if ( panelIndex ) {
panels.splice(panelIndex, 0, panel);
}else {
panels.push(panel);
}
};
return this;
});
var rowMixin = Barricade.Blueprint.create(function() {
var self = this,
items = self._getContents(),
rows;
self.getRows = function() {
if ( rows === undefined ) {
rows = utils.groupByMetaKey(items, 'row').map(function(items) {
return Object.create(rowProto).create(items);
});
}
return rows;
};
self.on('change', function(op) {
console.log(arguments);
if ( op == 'add' ) {
var items = self._getContents();
utils.groupByMetaKey(items, 'row').forEach(function(items) {
rows.push(Object.create(rowProto).create(items));
})
} else if ( op == 'remove' ) {
}
});
});
var groupMixin = Barricade.Blueprint.create(function() {
var self = this,
additive = utils.getMeta(self, 'additive');
rowMixin.call(self);
if ( additive === undefined ) {
additive = true;
}
self.isAdditive = function() {
return additive;
};
self.setType('group');
return self;
});
return {
panelmixin: panelMixin,
groupmixin: groupMixin,
rowmixin: rowMixin
}
}])
})();

View File

@ -0,0 +1,83 @@
/**
* Created by tsufiev on 2/24/15.
*/
(function(){
angular.module('hz')
.factory('merlin.utils', function() {
Array.prototype.condense = function() {
return this.filter(function(el) {
return el !== undefined && el != null;
});
};
var _id_counter = 0;
function getNewId() {
_id_counter++;
return 'id-' + _id_counter;
}
function groupByMetaKey(sequence, metaKey, insertAtBeginning) {
var newSequence = [], defaultBucket = [],
index;
sequence.forEach(function(item) {
index = getMeta(item, metaKey);
if ( index !== undefined ) {
if ( !newSequence[index] ) {
newSequence[index] = [];
newSequence[index][metaKey] = index;
}
newSequence[index].push(item);
} else {
defaultBucket.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) {
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;
}
return {
getMeta: getMeta,
getNewId: getNewId,
groupByMetaKey: groupByMetaKey,
makeTitle: makeTitle,
getNextIDSuffix: getNextIDSuffix,
enhanceItemWithID: enhanceItemWithID
}
})
})();

View File

@ -1,820 +0,0 @@
// Copyright 2014 Drago Rosson
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Barricade = (function () {
"use strict";
var Blueprint = {
create: function (f) {
var g = function () {
if (this.hasOwnProperty('_parents')) {
this._parents.push(g);
} else {
Object.defineProperty(this, '_parents', {
value: [g]
});
}
return f.apply(this, arguments);
};
return g;
}
};
var Extendable = Blueprint.create(function () {
function forInKeys(obj) {
var key,
keys = [];
for (key in obj) {
keys.push(key);
}
return keys;
}
function isPlainObject(obj) {
return getType(obj) === Object &&
Object.getPrototypeOf(Object.getPrototypeOf(obj)) === null;
}
function extend(extension) {
function addProperty(object, prop) {
return Object.defineProperty(object, prop, {
enumerable: true,
writable: true,
configurable: true,
value: extension[prop]
});
}
// add properties to extended object
return Object.keys(extension).reduce(addProperty,
Object.create(this));
}
function deepClone(object) {
if (isPlainObject(object)) {
return forInKeys(object).reduce(function (clone, key) {
clone[key] = deepClone(object[key]);
return clone;
}, {});
}
return object;
}
function merge(target, source) {
forInKeys(source).forEach(function (key) {
if (target.hasOwnProperty(key) &&
isPlainObject(target[key]) &&
isPlainObject(source[key])) {
merge(target[key], source[key]);
} else {
target[key] = deepClone(source[key]);
}
});
}
return Object.defineProperty(this, 'extend', {
enumerable: false,
writable: false,
value: function (extension, schema) {
if (schema) {
extension._schema = '_schema' in this ?
deepClone(this._schema) : {};
merge(extension._schema, schema);
}
return extend.call(this, extension);
}
});
});
var InstanceofMixin = Blueprint.create(function () {
return Object.defineProperty(this, 'instanceof', {
enumerable: false,
value: function (proto) {
var _instanceof = this.instanceof,
subject = this;
function hasMixin(obj, mixin) {
return obj.hasOwnProperty('_parents') &&
obj._parents.some(function (_parent) {
return _instanceof.call(_parent, mixin);
});
}
do {
if (subject === proto ||
hasMixin(subject, proto)) {
return true;
}
subject = Object.getPrototypeOf(subject);
} while (subject);
return false;
}
});
});
var Identifiable = Blueprint.create(function (id) {
this.getID = function () {
return id;
};
this.setID = function (newID) {
id = newID;
this.emit('change', 'id');
};
});
var Omittable = Blueprint.create(function (isUsed) {
this.isUsed = function () {
// If required, it has to be used.
return this.isRequired() || isUsed;
};
this.setIsUsed = function (newUsedValue) {
isUsed = !!newUsedValue;
};
this.on('change', function () {
isUsed = !this.isEmpty();
});
});
var Deferrable = Blueprint.create(function (schema) {
var self = this,
deferred;
function resolver(neededValue) {
var ref = schema['@ref'].resolver(self, neededValue);
if (ref === undefined) {
logError('Could not resolve "' +
JSON.stringify(self.toJSON()) + '"');
}
return ref;
}
if (schema.hasOwnProperty('@ref')) {
deferred = Deferred.create(schema['@ref'].needs, resolver);
}
this.resolveWith = function (obj) {
var allResolved = true;
if (deferred && !deferred.isResolved()) {
if (deferred.needs(obj)) {
this.emit('replace', deferred.resolve(obj));
} else {
allResolved = false;
}
}
if (this.instanceof(Container)) {
this.each(function (index, value) {
if (!value.resolveWith(obj)) {
allResolved = false;
}
});
}
return allResolved;
};
});
var Validatable = Blueprint.create(function (schema) {
var constraints = schema['@constraints'],
error = null;
if (getType(constraints) !== Array) {
constraints = [];
}
this.hasError = function () { return error !== null; };
this.getError = function () { return error || ''; };
this._validate = function (value) {
function getConstraintMessage(i, lastMessage) {
if (lastMessage !== true) {
return lastMessage;
} else if (i < constraints.length) {
return getConstraintMessage(i + 1, constraints[i](value));
}
return null;
}
error = getConstraintMessage(0, true);
return !this.hasError();
};
this.addConstraint = function (newConstraint) {
constraints.push(newConstraint);
};
});
var Enumerated = Blueprint.create(function(enum_) {
var self = this;
function getEnum() {
return (typeof enum_ === 'function') ? enum_.call(self) : enum_;
}
this.getEnumLabels = function () {
var curEnum = getEnum();
if (getType(curEnum[0]) === Object) {
return curEnum.map(function (value) { return value.label; });
} else {
return curEnum;
}
};
this.getEnumValues = function () {
var curEnum = getEnum();
if (getType(curEnum[0]) === Object) {
return curEnum.map(function (value) { return value.value; });
} else {
return curEnum;
}
};
this.addConstraint(function (value) {
return (self.getEnumValues().indexOf(value) > -1) ||
'Value can only be one of ' + self.getEnumLabels().join(', ');
});
});
var Observable = Blueprint.create(function () {
var events = {};
function hasEvent(eventName) {
return events.hasOwnProperty(eventName);
}
// Adds listener for event
this.on = function (eventName, callback) {
if (!hasEvent(eventName)) {
events[eventName] = [];
}
events[eventName].push(callback);
};
// Removes listener for event
this.off = function (eventName, callback) {
var index;
if (hasEvent(eventName)) {
index = events[eventName].indexOf(callback);
if (index > -1) {
events[eventName].splice(index, 1);
}
}
};
this.emit = function (eventName) {
var args = arguments; // Must come from correct scope
if (events.hasOwnProperty(eventName)) {
events[eventName].forEach(function (callback) {
// Call with emitter as context and pass all but eventName
callback.apply(this, Array.prototype.slice.call(args, 1));
}, this);
}
};
});
var Deferred = {
create: function (classGetter, onResolve) {
var self = Object.create(this);
self._isResolved = false;
self._classGetter = classGetter;
self._onResolve = onResolve;
return self;
},
resolve: function (obj) {
var ref;
if (this._isResolved) {
throw new Error('Deferred already resolved');
}
ref = this._onResolve(obj);
if (ref !== undefined) {
this._isResolved = true;
return ref;
}
},
isResolved: function () {
return this._isResolved;
},
needs: function (obj) {
return obj.instanceof(this._classGetter());
}
};
var Base = Extendable.call(InstanceofMixin.call({
create: function (json, parameters) {
var self = this.extend({}),
schema = self._schema,
type = schema['@type'],
isUsed;
self._parameters = parameters = parameters || {};
if (schema.hasOwnProperty('@inputMassager')) {
json = schema['@inputMassager'](json);
}
isUsed = self._setData(json);
if (schema.hasOwnProperty('@toJSON')) {
self.toJSON = schema['@toJSON'];
}
Observable.call(self);
Omittable.call(self, isUsed);
Deferrable.call(self, schema);
Validatable.call(self, schema);
if (schema.hasOwnProperty('@enum')) {
Enumerated.call(self, schema['@enum']);
}
if (parameters.hasOwnProperty('id')) {
Identifiable.call(self, parameters.id);
}
return self;
},
_setData: function(json) {
var isUsed = true,
type = this._schema['@type'];
if (getType(json) !== type) {
if (json) {
logError("Type mismatch (json, schema)");
logVal(json, this._schema);
} else {
isUsed = false;
}
// Replace bad type (does not change original)
json = this._getDefaultValue();
}
this._data = this._sift(json, this._parameters);
return isUsed;
},
_getDefaultValue: function () {
return this._schema.hasOwnProperty('@default')
? typeof this._schema['@default'] === 'function'
? this._schema['@default']()
: this._schema['@default']
: this._schema['@type']();
},
_sift: function () {
throw new Error("sift() must be overridden in subclass");
},
_safeInstanceof: function (instance, class_) {
return typeof instance === 'object' &&
('instanceof' in instance) &&
instance.instanceof(class_);
},
getPrimitiveType: function () {
return this._schema['@type'];
},
isRequired: function () {
return this._schema['@required'] !== false;
},
isEmpty: function () {
throw new Error('Subclass should override isEmpty()');
}
}));
var Container = Base.extend({
create: function (json, parameters) {
var self = Base.create.call(this, json, parameters),
allDeferred = [];
function attachListeners(key) {
self._attachListeners(key);
self.get(key)._container = self;
}
self.on('_addedElement', function (key) {
attachListeners(key);
self._tryResolveOn(self.get(key));
});
self.each(attachListeners);
self.each(function (index, value) {
value.resolveWith(self);
});
return self;
},
_tryResolveOn: function (value) {
if (!value.resolveWith(this)) {
this.emit('_resolveUp', value);
}
},
_attachListeners: function (key) {
var self = this,
element = this.get(key),
events = {
'childChange': function (child) {
self.emit('childChange', child);
},
'change': function () {
// 'this' is set to callee, no typo
events.childChange(this);
},
'replace': function (newValue) {
self.set(key, newValue);
self._tryResolveOn(newValue);
},
'_resolveUp': function (value) {
self._tryResolveOn(value);
},
'removeFrom': function (container) {
if (container === self) {
Object.keys(events).forEach(function (eName) {
element.on(eName, events[eName]);
});
}
}
};
Object.keys(events).forEach(function (eName) {
element.on(eName, events[eName]);
});
},
_getKeyClass: function (key) {
return this._schema[key].hasOwnProperty('@class')
? this._schema[key]['@class']
: BarricadeMain.create(this._schema[key]);
},
_keyClassCreate: function (key, keyClass, json, parameters) {
return this._schema[key].hasOwnProperty('@factory')
? this._schema[key]['@factory'](json, parameters)
: keyClass.create(json, parameters);
},
_isCorrectType: function (instance, class_) {
var self = this;
function isRefTo() {
if (typeof class_._schema['@ref'].to === 'function') {
return self._safeInstanceof(instance,
class_._schema['@ref'].to());
} else if (typeof class_._schema['@ref'].to === 'object') {
return self._safeInstanceof(instance,
class_._schema['@ref'].to);
}
throw new Error('Ref.to was ' + class_._schema['@ref'].to);
}
return this._safeInstanceof(instance, class_) ||
(class_._schema.hasOwnProperty('@ref') && isRefTo());
},
set: function (key, value) {
this.get(key).emit('removeFrom', this);
this._doSet(key, value);
this._attachListeners(key);
}
});
var Arraylike = Container.extend({
create: function (json, parameters) {
if (!this.hasOwnProperty('_elementClass')) {
Object.defineProperty(this, '_elementClass', {
enumerable: false,
writable: true,
value: this._getKeyClass(this._elSymbol)
});
}
return Container.create.call(this, json, parameters);
},
_elSymbol: '*',
_sift: function (json, parameters) {
return json.map(function (el) {
return this._keyClassCreate(this._elSymbol,
this._elementClass, el);
}, this);
},
get: function (index) {
return this._data[index];
},
each: function (functionIn, comparatorIn) {
var arr = this._data.slice();
if (comparatorIn) {
arr.sort(comparatorIn);
}
arr.forEach(function (value, index) {
functionIn(index, value);
});
},
toArray: function () {
return this._data.slice(); // Shallow copy to prevent mutation
},
_doSet: function (index, newVal, newParameters) {
var oldVal = this._data[index];
if (this._isCorrectType(newVal, this._elementClass)) {
this._data[index] = newVal;
} else {
this._data[index] = this._keyClassCreate(
this._elSymbol, this._elementClass,
newVal, newParameters);
}
this.emit('change', 'set', index, this._data[index], oldVal);
},
length: function () {
return this._data.length;
},
isEmpty: function () {
return this._data.length === 0;
},
toJSON: function (ignoreUnused) {
return this._data.map(function (el) {
return el.toJSON(ignoreUnused);
});
},
push: function (newValue, newParameters) {
if (this._isCorrectType(newValue, this._elementClass)) {
this._data.push(newValue);
} else {
this._data.push(this._keyClassCreate(
this._elSymbol, this._elementClass,
newValue, newParameters));
}
this.emit('_addedElement', this._data.length - 1);
this.emit('change', 'add', this._data.length - 1);
},
remove: function (index) {
this._data[index].emit('removeFrom', this);
this._data.splice(index, 1);
this.emit('change', 'remove', index);
}
});
var Array_ = Arraylike.extend({});
var ImmutableObject = Container.extend({
create: function (json, parameters) {
var self = this;
if (!this.hasOwnProperty('_keyClasses')) {
Object.defineProperty(this, '_keyClasses', {
enumerable: false,
writable: true,
value: this.getKeys().reduce(function (classes, key) {
classes[key] = self._getKeyClass(key);
return classes;
}, {})
});
}
return Container.create.call(this, json, parameters);
},
_sift: function (json, parameters) {
var self = this;
return this.getKeys().reduce(function (objOut, key) {
objOut[key] = self._keyClassCreate(
key, self._keyClasses[key], json[key]);
return objOut;
}, {});
},
get: function (key) {
return this._data[key];
},
_doSet: function (key, newValue, newParameters) {
var oldVal = this._data[key];
if (this._schema.hasOwnProperty(key)) {
if (this._isCorrectType(newValue,
this._keyClasses[key])) {
this._data[key] = newValue;
} else {
this._data[key] = this._keyClassCreate(
key, this._keyClasses[key],
newValue, newParameters);
}
this.emit('change', 'set', key, this._data[key], oldVal);
} else {
console.error('object does not have key (key, schema)');
console.log(key, this._schema);
}
},
each: function (functionIn, comparatorIn) {
var self = this,
keys = this.getKeys();
if (comparatorIn) {
keys.sort(comparatorIn);
}
keys.forEach(function (key) {
functionIn(key, self._data[key]);
});
},
isEmpty: function () {
return Object.keys(this._data).length === 0;
},
toJSON: function (ignoreUnused) {
var data = this._data;
return this.getKeys().reduce(function (jsonOut, key) {
if (ignoreUnused !== true || data[key].isUsed()) {
jsonOut[key] = data[key].toJSON(ignoreUnused);
}
return jsonOut;
}, {});
},
getKeys: function () {
return Object.keys(this._schema).filter(function (key) {
return key.charAt(0) !== '@';
});
}
});
var MutableObject = Arraylike.extend({
_elSymbol: '?',
_sift: function (json, parameters) {
return Object.keys(json).map(function (key) {
return this._keyClassCreate(
this._elSymbol, this._elementClass,
json[key], {id: key});
}, this);
},
getIDs: function () {
return this.toArray().map(function (value) {
return value.getID();
});
},
getPosByID: function(id) {
return this.toArray().map(function (value) {
return value.getID();
}).indexOf(id);
},
getByID: function (id) {
var pos = this.getPosByID(id);
return this.get(pos);
},
contains: function (element) {
return this.toArray().some(function (value) {
return element === value;
});
},
toJSON: function (ignoreUnused) {
return this.toArray().reduce(function (jsonOut, element) {
if (jsonOut.hasOwnProperty(element.getID())) {
logError("ID encountered multiple times: " +
element.getID());
} else {
jsonOut[element.getID()] =
element.toJSON(ignoreUnused);
}
return jsonOut;
}, {});
},
push: function (newJson, newParameters) {
if (getType(newParameters) !== Object ||
!newParameters.hasOwnProperty('id')) {
logError('ID should be passed in ' +
'with parameters object');
} else {
Array_.push.call(this, newJson, newParameters);
}
},
});
var Primitive = Base.extend({
_sift: function (json, parameters) {
return json;
},
get: function () {
return this._data;
},
set: function (newVal) {
var schema = this._schema;
function typeMatches(newVal) {
return getType(newVal) === schema['@type'];
}
if (typeMatches(newVal) && this._validate(newVal)) {
this._data = newVal;
this.emit('validation', 'succeeded');
this.emit('change');
} else if (this.hasError()) {
this.emit('validation', 'failed');
} else {
logError("Setter - new value did not match " +
"schema (newVal, schema)");
logVal(newVal, schema);
}
},
isEmpty: function () {
if (this._schema['@type'] === Array) {
return this._data.length === 0;
} else if (this._schema['@type'] === Object) {
return Object.keys(this._data).length === 0;
} else {
return this._data === this._schema['@type']();
}
},
toJSON: function () {
return this._data;
}
});
var getType = (function () {
var toString = Object.prototype.toString,
types = {
'boolean': Boolean,
'number': Number,
'string': String,
'[object Array]': Array,
'[object Date]': Date,
'[object Function]': Function,
'[object RegExp]': RegExp
};
return function (val) {
return types[typeof val] ||
types[toString.call(val)] ||
(val ? Object : null);
};
}());
function logError(msg) {
console.error("Barricade: " + msg);
}
function logVal(val1, val2) {
if (val2) {
console.log(val1, val2);
} else {
console.log(val1);
}
}
var BarricadeMain = {};
BarricadeMain.create = function (schema) {
function schemaIsMutable() {
return schema.hasOwnProperty('?');
}
function schemaIsImmutable() {
return Object.keys(schema).some(function (key) {
return key.charAt(0) !== '@' && key !== '?';
});
}
if (schema['@type'] === Object && schemaIsImmutable()) {
return ImmutableObject.extend({_schema: schema});
} else if (schema['@type'] === Object && schemaIsMutable()) {
return MutableObject.extend({_schema: schema});
} else if (schema['@type'] === Array && schema.hasOwnProperty('*')) {
return Array_.extend({_schema: schema});
} else {
return Primitive.extend({_schema: schema});
}
};
BarricadeMain.getType = getType; // Very helpful function
BarricadeMain.Base = Base;
BarricadeMain.Container = Container;
BarricadeMain.Array = Array_;
BarricadeMain.ImmutableObject = ImmutableObject;
BarricadeMain.MutableObject = MutableObject;
BarricadeMain.Primitive = Primitive;
BarricadeMain.Blueprint = Blueprint;
BarricadeMain.Observable = Observable;
BarricadeMain.Deferrable = Deferrable;
BarricadeMain.Omittable = Omittable;
BarricadeMain.Identifiable = Identifiable;
BarricadeMain.Enumerated = Enumerated;
return BarricadeMain;
}());

View File

@ -0,0 +1,11 @@
<div class="panel panel-default merlin-panel">
<div class="panel-heading" ng-show="title">
<h4 class="panel-title">
<a data-toggle="collapse" data-target="#elem-{$ $id $}" href="#">{$ title $}</a>
<a href="#" ng-click="onRemove()"><i ng-show="removable" class="fa fa-times-circle pull-right"></i></a></h4>
</div>
<div id="elem-{$ $id $}" ng-class="title ? 'panel-collapse collapse in' : ''">
<div class="panel-body" ng-transclude>
</div>
</div>
</div>

View File

@ -0,0 +1,19 @@
<collapsible-group title="{$ title $}" on-add="value.add()">
<div class="three-columns" ng-repeat="(key, subvalue) in value.getValues() track by key">
<div class="left-column">
<div class="form-group">
<label for="elem-{$ $id $}.{$ key $}">
<editable value="key" label="New Name"></editable>
</label>
<div class="input-group">
<input id="elem-{$ $id $}.{$ key $}" type="text" class="form-control" ng-model="subvalue.value"
ng-model-options="{ getterSetter: true }">
<span class="input-group-btn">
<button class="btn btn-default fa fa-minus-circle" type="button"
ng-click="value.remove(key)"></button>
</span>
</div>
</div>
</div>
</div>
</collapsible-group>

View File

@ -0,0 +1,15 @@
<collapsible-group title="{$ title $}">
<div ng-repeat="row in value.getRows() track by row.id">
<div ng-class="{'three-columns': row.index !== undefined}">
<div ng-repeat="item in row.getItems() track by item.id"
ng-class="{'right-column': item.isAtomic() && $odd, 'left-column': item.isAtomic() && $even}">
<div class="form-group">
<label for="elem-{$ $id $}.{$ item.getID() $}">{$ item.getTitle() $}</label>
<input type="text" class="form-control" id="elem-{$ $id $}.{$ item.getID() $}" ng-model="item.value"
ng-model-options="{getterSetter: true}">
</div>
<div class="clearfix" ng-if="$odd"></div>
</div>
</div>
</div>
</collapsible-group>

View File

@ -0,0 +1,12 @@
<collapsible-group title="{$ title $}" additive="{$ value.isAdditive() $}"
on-add="value.add()">
<div ng-repeat="row in value.getRows() track by row.id">
<div ng-class="{'three-columns': row.index !== undefined }">
<div ng-repeat="item in row.getItems() track by item.id"
ng-class="{'right-column': item.isAtomic() && $odd, 'left-column': item.isAtomic() && $even}">
<typed-field title="{$ item.getTitle() $}" value="item" type="{$ item.getType() $}"></typed-field>
<div class="clearfix" ng-if="$odd"></div>
</div>
</div>
</div>
</collapsible-group>

View File

@ -1,11 +1,11 @@
<collapsible-group title="{$ spec.title || makeTitle(spec.name) $}" on-add="add(item[spec.name], '')">
<collapsible-group title="{$ title $}" on-add="value.add()">
<div class="three-columns">
<div class="left-column">
<div class="form-group" ng-repeat="subItem in item[spec.name] track by $index">
<div class="form-group" ng-repeat="subItem in value.getValues() track by $index">
<div class="input-group">
<input type="text" class="form-control" ng-model="subItem">
<input type="text" class="form-control" ng-model="subItem.value" ng-model-options="{ getterSetter: true }">
<span class="input-group-btn">
<button class="btn btn-default" ng-click="remove(item[spec.name], subItem)">
<button class="btn btn-default" ng-click="value.remove($index)">
<i class="fa fa-minus-circle"></i>
</button>
</span>

View File

@ -0,0 +1,5 @@
<div class="form-group">
<label for="elem-{$ $id $}">{$ title $}</label>
<input type="number" class="form-control" id="elem-{$ $id $}" ng-model="value.value"
ng-model-options="{ getterSetter: true }">
</div>

View File

@ -0,0 +1,5 @@
<div class="form-group">
<label for="elem-{$ $id $}">{$ title $}</label>
<input type="text" class="form-control" id="elem-{$ $id $}" ng-model="value.value"
ng-model-options="{ getterSetter: true }">
</div>

View File

@ -0,0 +1,5 @@
<div class="form-group">
<label for="elem-{$ $id $}">{$ title $}</label>
<textarea class="form-control" id="elem-{$ $id $}" ng-model="value.value"
ng-model-options="{ getterSetter: true }"></textarea>
</div>