Implement schema-driven Action layout rendering

Make use of repeat-ng `track by` clause to avoid unnecessary DOM
redraw by ng-repeat each time the scope changes.

Change-Id: I8e2513c5aeabd8ec4bbf72ed025bd3d5da08f94d
This commit is contained in:
Timur Sufiev 2014-12-31 07:53:49 -08:00
parent bd9220ac5a
commit 5b8f8dff14
10 changed files with 277 additions and 225 deletions

View File

@ -1,7 +1,7 @@
<div class="section">
<div class="three-columns">
<div class="both-columns">
<h5><a data-toggle="collapse" data-target="#{$ groupId $}" href="#">{$ groupTitle $}</a></h5>
<h5><a data-toggle="collapse" data-target="#{$ id $}" href="#">{$ title $}</a></h5>
</div>
<div ng-show="additive" class="add-btn button-column">
<button class="btn btn-default btn-sm pull-right"><i class="fa fa-plus"></i></button>
@ -10,6 +10,6 @@
<i class="fa fa-times-circle pull-right"></i>
</div>
</div>
<div id="{$ groupId $}" class="collapse in" ng-transclude>
<div id="{$ id $}" class="collapse in" ng-transclude>
</div>
</div>

View File

@ -1,10 +1,10 @@
<div class="panel panel-default merlin-panel">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-target="#{$ panelId $}" href="#">{$ panelTitle $}</a>
<a data-toggle="collapse" data-target="#{$ id $}" href="#">{$ title $}</a>
<i ng-show="removable" class="fa fa-times-circle pull-right"></i></h4>
</div>
<div id="{$ panelId $}" class="panel-collapse collapse in">
<div id="{$ id $}" class="panel-collapse collapse in">
<div class="panel-body" ng-transclude>
</div>
</div>

View File

@ -0,0 +1,13 @@
<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="{$ id $}.{$ key $}">
<editable value="key" label="New Name"></editable>
</label>
<div class="input-group">
<input type="text" class="form-control" ng-model="value">
<i class="fa fa-minus-circle input-group-addon"></i>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,10 @@
<collapsible-group title="{$ spec.title || makeTitle(spec.name) $}" additive="false">
<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="{$ id $}.{$ key $}">{$ item.title || makeTitle(item.name) $}</label>
<input type="text" class="form-control" id="{$ id $}.{$ key $}" ng-model="item.value">
</div>
</div>
</div>
</collapsible-group>

View File

@ -0,0 +1,12 @@
<collapsible-group title="{$ spec.title || makeTitle(spec.name) $}" additive="true">
<div class="three-columns">
<div class="left-column">
<div class="form-group" ng-repeat="item in item[spec.name] track by $index">
<div class="input-group">
<input type="text" class="form-control" ng-model="item">
<i class="fa fa-minus-circle input-group-addon"></i>
</div>
</div>
</div>
</div>
</collapsible-group>

View File

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

View File

@ -0,0 +1,64 @@
<collapsible-group title="{$ spec.title || makeTitle(spec.name) $}" additive="true">
<div class="three-columns" ng-repeat="subItem in item[spec.name] track by subItem.id"
ng-class="{dictionary: subItem.type == 'dictionary', list: subItem.type == 'list'}">
<div class="left-column">
<div class="form-group">
<label for="{$ id $}.$index">Key Type</label>
<select id="{$ id $}.$index" class="form-control" ng-model="subItem.type">
<option ng-repeat="value in ['string', 'list', 'dictionary']"
value="{$ value $}" ng-selected="subItem.type == value">{$ makeTitle(value) $}</option>
</select>
</div>
</div>
<div ng-switch="subItem.type">
<!-- draw string input -->
<div class="right-column" ng-switch-when="string">
<div class="form-group">
<label>&nbsp;</label>
<input type="text" class="form-control" ng-model="subItem.value">
</div>
</div>
<!-- END: draw string input -->
<!-- draw dictionary inputs -->
<div ng-switch-when="dictionary">
<div ng-repeat="(key, value) in subItem.value track by key">
<div ng-hide="$first" class="left-column"></div>
<div class="right-column">
<div class="form-group">
<label for="{$ id $}.{$ key $}">
<editable value="key" label="New Name"></editable>
</label>
<div class="input-group">
<input type="text" class="form-control" ng-model="value">
<i class="fa fa-minus-circle input-group-addon"></i>
</div>
</div>
</div>
<div ng-hide="$last" class="clearfix"></div>
<div ng-show="$last" class="add-btn button-column">
<button class="btn btn-default btn-sm pull-right"><i class="fa fa-plus"></i></button>
</div>
</div>
</div>
<!-- END: draw dictionary inputs -->
<!-- draw list inputs -->
<div ng-switch-when="list">
<div ng-repeat="value in subItem.value track by $index">
<div ng-hide="$first" class="left-column"></div>
<div class="right-column">
<label ng-show="$first">&nbsp;</label>
<div class="input-group">
<input type="text" class="form-control" ng-model="value">
<i class="fa fa-minus-circle input-group-addon"></i>
</div>
</div>
<div ng-hide="$last" class="clearfix"></div>
<div ng-show="$last" class="add-btn button-column">
<button class="btn btn-default btn-sm pull-right"><i class="fa fa-plus"></i></button>
</div>
</div>
</div>
<!-- END: draw list inputs -->
</div>
</div>
</collapsible-group>

View File

@ -5,63 +5,131 @@
(function() {
angular.module('hz')
.controller('actionsCtrl', function($scope) {
$scope.actions = [
[{
name: 'Name',
type: 'string',
value: 'Action1'
}, {
name: 'Base',
type: 'string',
value: 'nova.create_server'
}, {
name: 'Base Input',
type: 'fixedlist'
}, {
name: 'Input',
type: 'list',
value: [{
type: 'string',
value: ''
}]
}, {
name: 'Output',
type: 'varlist',
value: [{
type: 'string',
value: ''
}, {
type: 'dictionary',
value: [{
name: 'key1',
type: 'string',
value: ''
}, {
name: 'key2',
type: 'string',
value: ''
}]
}, {
type: 'list',
value: [{
type: 'string',
value: ''
}, {
type: 'string',
value: ''
}]
}]
}]];
.controller('workbookCtrl', function($scope, idGenerator) {
$scope.idGenerator = idGenerator;
$scope.baseTypes = {
'nova.create_server': [{
name: 'Flavor Id',
type: 'string'
$scope.data = {
actions: [{
id: 'action1',
name: 'Action1',
base: 'nova.create_server',
baseInput: {
flavorId: {
title: 'Flavor Id',
type: 'string'
},
imageId: {
title: 'Image Id',
type: 'string'
}
},
input: [''],
output: [{
id: 'varlist1',
type: 'string',
value: ''
}, {
id: 'varlist2',
type: 'dictionary',
value: {
key1: '',
key2: ''
}
}, {
id: 'varlist3',
type: 'list',
value: ['', '']
}]
}
]
};
$scope.schema = {
action: [{
name: 'name',
type: 'string',
group: 'one'
}, {
name: 'Image Id',
type: 'string'
}]
name: 'base',
type: 'string',
group: 'one'
}, {
name: 'baseInput',
type: 'frozendict',
group: ''
}, {
name: 'input',
type: 'list',
group: ''
}, {
name: 'output',
type: 'varlist',
group: ''
}
]
};
$scope.makeTitle = function(str) {
if ( !str ) {
return '';
}
var firstLetter = str.substr(0, 1).toUpperCase();
return firstLetter + str.substr(1);
};
$scope.getKeys = function(obj) {
return Object.keys(obj);
};
$scope.isAtomic = function(type) {
return ['string'].indexOf(type) > -1;
}
})
.controller('actionCtrl', function($scope) {
$scope.fixedFields = [['name', 'base']];
$scope.fields = ['baseInput', 'input', 'output'];
var actionBase = null,
baseTypes = {
'nova.create_server': {
flavorId: {
title: 'Flavor Id',
type: 'string'
},
imageId: {
title: 'Image Id',
type: 'string'
}
}
};
//$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) {
if ( !$scope.item.value ) {
$scope.item.value = {'Key1': ''};
}
})
.controller('listCtrl', function($scope) {
if ( !$scope.item.value ) {
$scope.item.value = [''];
}
})

View File

@ -19,7 +19,7 @@
templateUrl: '/static/mistral/js/angular-templates/editable-popup.html',
scope: {
label: '@',
value: '@'
value: '='
},
link: function(scope, element) {
angular.element(element).find('a[data-toggle="popover"]')
@ -73,14 +73,14 @@
templateUrl: '/static/mistral/js/angular-templates/collapsible-panel.html',
transclude: true,
scope: {
panelTitle: '@',
title: '@',
removable: '@'
},
compile: function(element, attrs) {
defaultSetter(attrs, 'removable', false);
return {
post: function(scope, element) {
scope.panelId = idGenerator();
post: function(scope, element, attrs) {
scope.id = idGenerator();
disableClickDefaultBehaviour(element);
}
}
@ -94,7 +94,7 @@
templateUrl: '/static/mistral/js/angular-templates/collapsible-group.html',
transclude: true,
scope: {
groupTitle: '@',
title: '@',
additive: '@',
removable: '@'
},
@ -103,7 +103,7 @@
defaultSetter(attrs, 'additive', false);
return {
post: function(scope, element) {
scope.groupId = idGenerator();
scope.id = idGenerator();
disableClickDefaultBehaviour(element);
}
}
@ -111,4 +111,20 @@
}
})
.directive('typedField', function($http, $templateCache, $compile, idGenerator) {
return {
restrict: 'E',
scope: true,
link: function(scope, element, attrs) {
scope.id = idGenerator();
$http.get(
'/static/mistral/js/angular-templates/fields/' + scope.spec.type + '.html',
{cache: $templateCache}).success(function(templateContent) {
element.replaceWith($compile(templateContent)(scope));
});
}
}
})
})();

View File

@ -27,7 +27,7 @@
{% block main %}
<h3>Create Workbook</h3>
<div id="create-workbook" class="fluid-container">
<div id="create-workbook" class="fluid-container" ng-controller="workbookCtrl">
<div class="well">
<div class="two-panels">
<div class="left-panel">
@ -71,155 +71,20 @@
</div>
</div>
<!-- Action added -->
<div ng-controller="actionsCtrl">
<collapsible-panel panel-title="Action1" removable="true">
<!-- 2 simple inputs in a single row -->
<div class="three-columns">
<div class="left-column">
<div class="form-group">
<label for="action1Name">Name</label>
<input type="text" class="form-control" id="action1Name" placeholder="Action1">
</div>
</div>
<div class="right-column">
<div class="form-group">
<label for="action1Base">Base</label>
<input type="text" class="form-control" id="action1Base" placeholder="nova.create_server">
</div>
<collapsible-panel ng-controller="actionCtrl" ng-repeat="item in data.actions track by item.id" title="{$ item.name $}" removable="true">
<div ng-repeat="specs in schema.action | groupBy: 'group' | toArray:true | orderBy: '-$key' track by specs.$key">
<div ng-class="{'three-columns': specs[0].group}">
<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>
<!-- collapsible dictionary section with fixed keys -->
<collapsible-group group-title="Base Input" additive="false">
<div class="three-columns">
<div class="left-column">
<div class="form-group">
<label for="action1BaseInputFlavorId">Flavor Id</label>
<input type="text" class="form-control" id="action1BaseInputFlavorId">
</div>
<div class="form-group">
<label for="action1BaseInputImageId">Image Id</label>
<input type="text" class="form-control" id="action1BaseInputImageId">
</div>
</div>
</div>
</collapsible-group>
<!-- collapsible keys list section -->
<collapsible-group group-title="Input" additive="true">
<div class="three-columns">
<div class="left-column">
<div class="form-group">
<div class="input-group">
<input type="text" class="form-control">
<i class="fa fa-minus-circle input-group-addon"></i>
</div>
</div>
</div>
</div>
</collapsible-group>
<!-- collapsible section with variable type = string|list|dictionary -->
<collapsible-group group-title="Output" additive="true">
<!-- string input type -->
<div class="three-columns">
<div class="left-column">
<div class="form-group">
<label for="collapseFourOuputF1">Key Type</label>
<select id="collapseFourOuputF1" class="form-control">
<option selected disabled>String</option>
<option disabled>List</option>
<option disabled>Dictionary</option>
</select>
</div>
</div>
<div class="right-column">
<div class="form-group">
<label>&nbsp;</label>
<input type="text" class="form-control">
</div>
</div>
</div>
<!-- dictionary input type -->
<div class="dictionary">
<div class="three-columns">
<div class="left-column">
<div class="form-group">
<label for="collapseFourOuputF2">Key Type</label>
<select id="collapseFourOuputF2" class="form-control">
<option disabled>String</option>
<option selected disabled>Dictionary</option>
<option disabled>List</option>
</select>
</div>
</div>
<div class="right-column">
<div class="form-group">
<label for="collapseFourOuputF3">
<editable value="Key1" label="New Name"></editable>
</label>
<div class="input-group" id="collapseFourOuputF3">
<input type="text" class="form-control">
<i class="fa fa-minus-circle input-group-addon"></i>
</div>
</div>
</div>
</div>
<div class="three-columns">
<div class="left-column"></div>
<div class="right-column">
<div class="form-group">
<label for="collapseFourOuputF4">
<editable value="Key2" label="New Name"></editable>
</label>
<div class="input-group" id="collapseFourOuputF4">
<input type="text" class="form-control">
<i class="fa fa-minus-circle input-group-addon"></i>
</div>
</div>
</div>
<div class="add-btn button-column">
<button class="btn btn-default btn-sm pull-right"><i class="fa fa-plus"></i></button>
</div>
</div>
</div>
<!-- list input type -->
<div class="list">
<div class="three-columns">
<div class="left-column">
<div class="form-group">
<label for="collapseFourOuputF5">Key Type</label>
<select id="collapseFourOuputF5" class="form-control">
<option disabled>String</option>
<option disabled>Dictionary</option>
<option selected disabled>List</option>
</select>
</div>
</div>
<div class="right-column">
<label>&nbsp;</label>
<div class="input-group">
<input type="text" class="form-control">
<i class="fa fa-minus-circle input-group-addon"></i>
</div>
</div>
</div>
<div class="three-columns">
<div class="left-column"></div>
<div class="right-column">
<div class="input-group">
<input type="text" class="form-control">
<i class="fa fa-minus-circle input-group-addon"></i>
</div>
</div>
<div class="add-btn button-column">
<button class="btn btn-default btn-sm pull-right"><i class="fa fa-plus"></i></button>
</div>
</div>
</div>
</collapsible-group>
</collapsible-panel>
</div>
</div>
</collapsible-panel>
<!-- panel with workflow -->
<div ng-controller="workflowsCtrl">
<collapsible-panel panel-title="Workflow1" removable="true">
<collapsible-panel title="Workflow1" removable="true">
<!-- 2 simple inputs in a single row -->
<div class="three-columns">
<div class="left-column">
@ -239,7 +104,7 @@
</div>
</div>
<!-- collapsible keys list section -->
<collapsible-group group-title="Input" additive="true">
<collapsible-group title="Input" additive="true">
<div class="three-columns">
<div class="left-column">
<div class="form-group">
@ -252,7 +117,7 @@
</div>
</collapsible-group>
<!-- collapsible section with variable type = string|list|dictionary -->
<collapsible-group group-title="Output" additive="true">
<collapsible-group title="Output" additive="true">
<!-- string input type -->
<div class="three-columns">
<div class="left-column">
@ -273,21 +138,21 @@
</div>
</div>
</collapsible-group>
<collapsible-group group-title="Task Defaults" additive="false">
<collapsible-group group-title="On error" additive="true">
<collapsible-group title="Task Defaults" additive="false">
<collapsible-group title="On error" additive="true">
<yaql-field-combined></yaql-field-combined>
<yaql-field-combined></yaql-field-combined>
</collapsible-group>
<collapsible-group group-title="On success" additive="true">
<collapsible-group title="On success" additive="true">
<yaql-field-combined></yaql-field-combined>
</collapsible-group>
<collapsible-group group-title="On complete" additive="true">
<collapsible-group title="On complete" additive="true">
<yaql-field-combined></yaql-field-combined>
<yaql-field-combined></yaql-field-combined>
</collapsible-group>
</collapsible-group>
<collapsible-group group-title="Tasks" additive="true">
<collapsible-group group-title="TaskAutoName1" removable="true">
<collapsible-group title="Tasks" additive="true">
<collapsible-group title="TaskAutoName1" removable="true">
<div class="three-columns">
<div class="left-column">
<div class="form-group">
@ -315,7 +180,7 @@
</div>
</div>
</div>
<collapsible-group group-title="Input" additive="true">
<collapsible-group title="Input" additive="true">
<div class="three-columns">
<div class="left-column">
<div class="form-group">
@ -330,7 +195,7 @@
</div>
</div>
</collapsible-group>
<collapsible-group group-title="Publish" additive="true">
<collapsible-group title="Publish" additive="true">
<div class="three-columns">
<div class="left-column">
<div class="form-group">
@ -345,16 +210,16 @@
</div>
</div>
</collapsible-group>
<collapsible-group group-title="On error" additive="true">
<collapsible-group title="On error" additive="true">
<yaql-field-combined></yaql-field-combined>
</collapsible-group>
<collapsible-group group-title="On success" additive="true">
<collapsible-group title="On success" additive="true">
<yaql-field-combined></yaql-field-combined>
</collapsible-group>
<collapsible-group group-title="On complete" additive="true">
<collapsible-group title="On complete" additive="true">
<yaql-field-combined></yaql-field-combined>
</collapsible-group>
<collapsible-group group-title="Policies">
<collapsible-group title="Policies">
<div class="two-columns">
<div class="left-column">
<div class="form-group">