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:
parent
389af84bb4
commit
fc14e3c993
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
36
extensions/mistral/static/mistral/js/mistral.directives.js
Normal file
36
extensions/mistral/static/mistral/js/mistral.directives.js
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
})();
|
17
extensions/mistral/static/mistral/js/mistral.init.js
Normal file
17
extensions/mistral/static/mistral/js/mistral.init.js
Normal 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);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})();
|
@ -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);
|
||||||
|
};
|
||||||
|
|
||||||
|
}])
|
||||||
|
})();
|
440
extensions/mistral/static/mistral/js/mistral.workbook.models.js
Normal file
440
extensions/mistral/static/mistral/js/mistral.workbook.models.js
Normal 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;
|
||||||
|
}])
|
||||||
|
})();
|
@ -14,7 +14,9 @@
|
|||||||
|
|
||||||
angular.module('hz')
|
angular.module('hz')
|
||||||
|
|
||||||
.controller('workbookCtrl', function($scope) {
|
.controller('workbookCtrl', function($scope, workbook, $filter) {
|
||||||
|
$scope.workbook = workbook;
|
||||||
|
|
||||||
$scope.defaults = {
|
$scope.defaults = {
|
||||||
'actions': {
|
'actions': {
|
||||||
name: 'Action1',
|
name: 'Action1',
|
||||||
@ -280,6 +282,8 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$scope.makeTitle = function(str) {
|
$scope.makeTitle = function(str) {
|
||||||
if ( !str ) {
|
if ( !str ) {
|
||||||
return '';
|
return '';
|
||||||
@ -293,7 +297,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.isAtomic = function(type) {
|
$scope.isAtomic = function(type) {
|
||||||
return ['string', 'text'].indexOf(type) > -1;
|
return ['string', 'text', 'number'].indexOf(type) > -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.remove = function(parent, item) {
|
$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) {
|
.controller('dictionaryCtrl', function($scope) {
|
213
extensions/mistral/static/mistral/js/obsolete/schema-alt.js
Normal file
213
extensions/mistral/static/mistral/js/obsolete/schema-alt.js
Normal 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();
|
||||||
|
})
|
||||||
|
})();
|
||||||
|
|
@ -301,16 +301,29 @@ types.Mistral.Workbook = Barricade.create({
|
|||||||
|
|
||||||
'version': {
|
'version': {
|
||||||
'@type': Number,
|
'@type': Number,
|
||||||
'@meta': {'groups': ['panel1']},
|
'@enum': function() { return [2]; },
|
||||||
|
'@meta': {
|
||||||
|
'index': 2,
|
||||||
|
'panelIndex': 0,
|
||||||
|
'row': 1
|
||||||
|
},
|
||||||
'@default': 2
|
'@default': 2
|
||||||
},
|
},
|
||||||
'name': {
|
'name': {
|
||||||
'@type': String,
|
'@type': String,
|
||||||
'@meta': {'groups': ['panel1']}
|
'@meta': {
|
||||||
|
'index': 0,
|
||||||
|
'panelIndex': 0,
|
||||||
|
'row': 0
|
||||||
|
}
|
||||||
},
|
},
|
||||||
'description': {
|
'description': {
|
||||||
'@type': String,
|
'@type': String,
|
||||||
'@meta': {'groups': ['panel1']},
|
'@meta': {
|
||||||
|
'index': 1,
|
||||||
|
'panelIndex': 0,
|
||||||
|
'row': 0
|
||||||
|
},
|
||||||
'@required': false
|
'@required': false
|
||||||
},
|
},
|
||||||
'actions': {
|
'actions': {
|
@ -17,9 +17,10 @@
|
|||||||
|
|
||||||
.run(function($http, $templateCache) {
|
.run(function($http, $templateCache) {
|
||||||
var fields = ['dictionary', 'frozendict', 'list', 'string',
|
var fields = ['dictionary', 'frozendict', 'list', 'string',
|
||||||
'varlist', 'text', 'group', 'yaqllist'];
|
'varlist', 'text', 'group', 'yaqllist', 'number'
|
||||||
|
];
|
||||||
fields.forEach(function(field) {
|
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) {
|
$http.get(base + field + '.html').success(function(templateContent) {
|
||||||
$templateCache.put(field, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
})();
|
})();
|
@ -1,25 +1,28 @@
|
|||||||
<collapsible-group title="{$ spec.title || makeTitle(spec.name) $}"
|
<collapsible-group title="{$ title $}"
|
||||||
on-add="add(item[spec.name], {type: 'string', value: '', id: 'varlist'+item[spec.name].length})">
|
on-add="value.add()">
|
||||||
<div class="three-columns" ng-repeat="subItem in item[spec.name] track by subItem.id"
|
<div class="three-columns" ng-repeat="subItem in value.getValues() track by $index"
|
||||||
ng-class="{dictionary: subItem.type == 'dictionary', list: subItem.type == 'list'}">
|
ng-class="subItem.get('type').get()">
|
||||||
<div class="left-column">
|
<div class="left-column">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="elem-{$ $id $}.$index">Key Type</label>
|
<label for="elem-{$ $id $}.$index">Key Type</label>
|
||||||
<select id="elem-{$ $id $}.$index" class="form-control" ng-model="subItem.type">
|
<select id="elem-{$ $id $}.$index" class="form-control"
|
||||||
<option ng-repeat="value in ['string', 'list', 'dictionary']"
|
ng-model="subItem.get('type').value" ng-model-options="{getterSetter: true}">
|
||||||
value="{$ value $}" ng-selected="subItem.type == value">{$ makeTitle(value) $}</option>
|
<option ng-repeat="value in subItem.get('type').getEnumValues()"
|
||||||
|
value="{$ value $}"
|
||||||
|
ng-selected="subItem.get('type').get() == value">{$ value $}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ng-switch="subItem.type">
|
<div ng-switch="subItem.get('type').value()">
|
||||||
<!-- draw string input -->
|
<!-- draw string input -->
|
||||||
<div class="right-column" ng-switch-when="string">
|
<div class="right-column" ng-switch-when="string">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label> </label>
|
<label> </label>
|
||||||
<div class="input-group">
|
<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">
|
<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>
|
<i class="fa fa-minus-circle"></i>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
@ -28,8 +31,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- END: draw string input -->
|
<!-- END: draw string input -->
|
||||||
<!-- draw dictionary inputs -->
|
<!-- draw dictionary inputs -->
|
||||||
<div ng-switch-when="dictionary" ng-controller="dictionaryCtrl">
|
<div ng-switch-when="dictionary">
|
||||||
<div ng-repeat="(key, value) in subItem.value track by key">
|
<div ng-repeat="(key, value) in subItem.get('value').getValues() track by key">
|
||||||
<div ng-hide="$first" class="left-column"></div>
|
<div ng-hide="$first" class="left-column"></div>
|
||||||
<div class="right-column">
|
<div class="right-column">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -37,9 +40,10 @@
|
|||||||
<editable value="key" label="New Name"></editable>
|
<editable value="key" label="New Name"></editable>
|
||||||
</label>
|
</label>
|
||||||
<div class="input-group">
|
<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">
|
<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>
|
<i class="fa fa-minus-circle"></i>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
@ -48,7 +52,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div ng-hide="$last" class="clearfix"></div>
|
<div ng-hide="$last" class="clearfix"></div>
|
||||||
<div ng-show="$last" class="add-btn button-column">
|
<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>
|
<i class="fa fa-plus"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -56,16 +60,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- END: draw dictionary inputs -->
|
<!-- END: draw dictionary inputs -->
|
||||||
<!-- draw list inputs -->
|
<!-- draw list inputs -->
|
||||||
<div ng-switch-when="list" ng-controller="listCtrl">
|
<div ng-switch-when="list">
|
||||||
<div ng-repeat="value in subItem.value track by $index">
|
<div ng-repeat="value in subItem.get('value').getValues() track by $index">
|
||||||
<div ng-hide="$first" class="left-column"></div>
|
<div ng-hide="$first" class="left-column"></div>
|
||||||
<div class="right-column">
|
<div class="right-column">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label ng-show="$first"> </label>
|
<label ng-show="$first"> </label>
|
||||||
<div class="input-group">
|
<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">
|
<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>
|
<i class="fa fa-minus-circle"></i>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
@ -74,7 +79,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div ng-hide="$last" class="clearfix"></div>
|
<div ng-hide="$last" class="clearfix"></div>
|
||||||
<div ng-show="$last" class="add-btn button-column" ng-class="{'varlist-1st-row': !$index}">
|
<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>
|
<i class="fa fa-plus"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
@ -11,9 +11,18 @@
|
|||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
{% include "horizon/_scripts.html" %}
|
{% include "horizon/_scripts.html" %}
|
||||||
<script type="text/javascript" src="{{ STATIC_URL }}mistral/js/services.js"></script>
|
<script type="text/javascript" src="{{ STATIC_URL }}merlin/js/lib/barricade.js"></script>
|
||||||
<script type="text/javascript" src="{{ STATIC_URL }}mistral/js/directives.js"></script>
|
<script type="text/javascript" src="{{ STATIC_URL }}merlin/js/merlin.directives.js"></script>
|
||||||
<script type="text/javascript" src="{{ STATIC_URL }}mistral/js/controllers.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 %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
@ -32,12 +41,12 @@
|
|||||||
<div class="two-panels">
|
<div class="two-panels">
|
||||||
<div class="left-panel">
|
<div class="left-panel">
|
||||||
<div class="pull-left">
|
<div class="pull-left">
|
||||||
<h4><strong>Workbook1</strong></h4>
|
<h4><strong>{$ workbook.get('name') $}</strong></h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<div class="table-actions clearfix">
|
<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 ng-click="addAction()" 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="addWorkflow()" class="btn btn-default btn-sm"><span class="fa fa-plus">Add Workflow</span></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -51,33 +60,8 @@
|
|||||||
<!-- Data panel start -->
|
<!-- Data panel start -->
|
||||||
<div class="two-panels">
|
<div class="two-panels">
|
||||||
<div class="left-panel">
|
<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 -->
|
<!-- panel with workflow -->
|
||||||
<div ng-controller="workflowsCtrl">
|
<div>
|
||||||
<panel title="Workflow1" removable="true">
|
<panel title="Workflow1" removable="true">
|
||||||
<!-- 2 simple inputs in a single row -->
|
<!-- 2 simple inputs in a single row -->
|
||||||
<div class="three-columns">
|
<div class="three-columns">
|
||||||
@ -274,6 +258,19 @@ name: Workbook1
|
|||||||
description: </pre>
|
description: </pre>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
<!-- page footer -->
|
<!-- page footer -->
|
||||||
|
@ -155,11 +155,11 @@ function isScope(obj) {
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @ngdoc filter
|
* @ngdoc filter
|
||||||
* @name a8m.angular
|
* @name a8m.templates
|
||||||
* @kind function
|
* @kind function
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* reference to angular function
|
* reference to templates function
|
||||||
*/
|
*/
|
||||||
|
|
||||||
angular.module('a8m.angular', [])
|
angular.module('a8m.angular', [])
|
||||||
@ -2022,7 +2022,7 @@ angular.module('a8m.filter-watcher', [])
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @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.
|
* if clear cache when the digest cycle end.
|
||||||
*/
|
*/
|
||||||
function cleanStateless() {
|
function cleanStateless() {
|
||||||
@ -2075,7 +2075,7 @@ angular.module('a8m.filter-watcher', [])
|
|||||||
var hashKey = getHashKey(filterName, args);
|
var hashKey = getHashKey(filterName, args);
|
||||||
//store result in `$$cache` container
|
//store result in `$$cache` container
|
||||||
$$cache[hashKey] = result;
|
$$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
|
// add to `$destroy` listener, a cleaner callback
|
||||||
if(isScope(scope)) {
|
if(isScope(scope)) {
|
||||||
addListener(scope, hashKey);
|
addListener(scope, hashKey);
|
||||||
@ -2114,7 +2114,7 @@ angular.module('a8m.filter-watcher', [])
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @ngdoc module
|
* @ngdoc module
|
||||||
* @name angular.filters
|
* @name templates.filters
|
||||||
* @description
|
* @description
|
||||||
* Bunch of useful filters for angularJS
|
* Bunch of useful filters for angularJS
|
||||||
*/
|
*/
|
1354
merlin/static/merlin/js/lib/barricade.js
Normal file
1354
merlin/static/merlin/js/lib/barricade.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* Created by tsufiev on 12/29/14.
|
* Created by tsufiev on 2/24/15.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
function disableClickDefaultBehaviour(element) {
|
function disableClickDefaultBehaviour(element) {
|
||||||
angular.element(element).find('a[data-toggle="collapse"]')
|
element.find('a[data-toggle="collapse"]')
|
||||||
.on('click', function(e) {
|
.on('click', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return true;
|
return true;
|
||||||
@ -16,7 +15,7 @@
|
|||||||
.directive('editable', function() {
|
.directive('editable', function() {
|
||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
templateUrl: '/static/mistral/js/angular-templates/editable-popup.html',
|
templateUrl: '/static/merlin/templates/editable-popup.html',
|
||||||
scope: {
|
scope: {
|
||||||
label: '@',
|
label: '@',
|
||||||
value: '='
|
value: '='
|
||||||
@ -32,48 +31,18 @@
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|
||||||
.directive('yaqlFieldCombined', function() {
|
.directive('panel', function($parse) {
|
||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
templateUrl: '/static/mistral/js/angular-templates/yaql-field-combined.html',
|
templateUrl: '/static/merlin/templates/collapsible-panel.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',
|
|
||||||
transclude: true,
|
transclude: true,
|
||||||
scope: {
|
scope: {
|
||||||
title: '@',
|
title: '@',
|
||||||
onRemove: '&'
|
onRemove: '&'
|
||||||
},
|
},
|
||||||
link: function(scope, element, attrs) {
|
link: function(scope, element, attrs) {
|
||||||
|
scope.removable = $parse(attrs.removable)();
|
||||||
disableClickDefaultBehaviour(element);
|
disableClickDefaultBehaviour(element);
|
||||||
if ( attrs.onRemove ) {
|
|
||||||
scope.removable = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -81,7 +50,7 @@
|
|||||||
.directive('collapsibleGroup', function() {
|
.directive('collapsibleGroup', function() {
|
||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
templateUrl: '/static/mistral/js/angular-templates/collapsible-group.html',
|
templateUrl: '/static/merlin/templates/collapsible-group.html',
|
||||||
transclude: true,
|
transclude: true,
|
||||||
scope: {
|
scope: {
|
||||||
title: '@',
|
title: '@',
|
||||||
@ -103,12 +72,16 @@
|
|||||||
.directive('typedField', function($http, $templateCache, $compile) {
|
.directive('typedField', function($http, $templateCache, $compile) {
|
||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
scope: true,
|
scope: {
|
||||||
|
title: '@',
|
||||||
|
value: '=',
|
||||||
|
type: '@'
|
||||||
|
},
|
||||||
link: function(scope, element) {
|
link: function(scope, element) {
|
||||||
var template = $templateCache.get(scope.spec.type);
|
var template = $templateCache.get(scope.type);
|
||||||
element.replaceWith($compile(template)(scope));
|
element.replaceWith($compile(template)(scope));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
})();
|
})();
|
174
merlin/static/merlin/js/merlin.field.models.js
Normal file
174
merlin/static/merlin/js/merlin.field.models.js
Normal 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
|
||||||
|
};
|
||||||
|
}])
|
||||||
|
})();
|
19
merlin/static/merlin/js/merlin.init.js
Normal file
19
merlin/static/merlin/js/merlin.init.js
Normal 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);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})();
|
152
merlin/static/merlin/js/merlin.panel.models.js
Normal file
152
merlin/static/merlin/js/merlin.panel.models.js
Normal 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
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
})();
|
83
merlin/static/merlin/js/merlin.utils.js
Normal file
83
merlin/static/merlin/js/merlin.utils.js
Normal 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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})();
|
@ -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;
|
|
||||||
|
|
||||||
}());
|
|
11
merlin/static/merlin/templates/collapsible-panel.html
Normal file
11
merlin/static/merlin/templates/collapsible-panel.html
Normal 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>
|
19
merlin/static/merlin/templates/fields/dictionary.html
Normal file
19
merlin/static/merlin/templates/fields/dictionary.html
Normal 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>
|
15
merlin/static/merlin/templates/fields/frozendict.html
Normal file
15
merlin/static/merlin/templates/fields/frozendict.html
Normal 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>
|
12
merlin/static/merlin/templates/fields/group.html
Normal file
12
merlin/static/merlin/templates/fields/group.html
Normal 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>
|
@ -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="three-columns">
|
||||||
<div class="left-column">
|
<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">
|
<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">
|
<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>
|
<i class="fa fa-minus-circle"></i>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
5
merlin/static/merlin/templates/fields/number.html
Normal file
5
merlin/static/merlin/templates/fields/number.html
Normal 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>
|
5
merlin/static/merlin/templates/fields/string.html
Normal file
5
merlin/static/merlin/templates/fields/string.html
Normal 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>
|
5
merlin/static/merlin/templates/fields/text.html
Normal file
5
merlin/static/merlin/templates/fields/text.html
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user