Collapse/expand job_events list using the api parent/child relationship

This commit is contained in:
chouseknecht 2013-06-18 17:20:25 -04:00
parent c9da8294df
commit 2abd33cf22
9 changed files with 215 additions and 35 deletions

View File

@ -436,4 +436,17 @@
.list-header .icon-sort-down,
.list-header .icon-sort-up {
color: #36454F;
}
/* job_events syles */
tr td i {
float: none;
margin-right: 10px;
padding-top: 3px;
padding-left: 0;
margin-left: 0;
}
#event_display-header {
min-width: 250px;
}

View File

@ -52,7 +52,8 @@ angular.module('ansible', [
'JobHostDefinition',
'GroupsHelper',
'HostsHelper',
'ParseHelper'
'ParseHelper',
'ChildrenHelper'
])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.

View File

@ -12,21 +12,50 @@
function JobEventsList ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, JobEventList,
GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller,
ClearScope, ProcessErrors, GetBasePath, LookUpInit)
ClearScope, ProcessErrors, GetBasePath, LookUpInit, ToggleChildren)
{
ClearScope('htmlTemplate');
var list = JobEventList;
list.base = $location.path();
var defaultUrl = GetBasePath('jobs') + $routeParams.id + '/job_events/';
var defaultUrl = GetBasePath('jobs') + $routeParams.id + '/job_events/?parent__isnull=1';
var view = GenerateList;
var base = $location.path().replace(/^\//,'').split('/')[0];
var scope = view.inject(list, { mode: 'edit' });
scope.selected = [];
if (scope.RemovePostRefresh) {
scope.RemovePostRefresh();
}
scope.RemovePostRefresh = scope.$on('PostRefresh', function() {
// Initialize the parent levels
var set = scope[list.name];
for (var i=0; i < set.length; i++) {
set[i].event_display = set[i].event_display.replace(/^\u00a0*/g,'');
if (set[i].parent == null && set[i]['ngclick'] === undefined && set[i]['ngicon'] == undefined) {
set[i].parent = 0;
set[i]['ngclick'] = "toggleChildren(" + set[i].id + ", \"" + set[i].related.children + "\")";
set[i]['ngicon'] = 'icon-expand-alt';
set[i]['level'] = 0;
set[i]['spaces'] = 0;
}
}
});
SearchInit({ scope: scope, set: 'jobevents', list: list, url: defaultUrl });
PaginateInit({ scope: scope, list: list, url: defaultUrl });
scope.search(list.iterator);
scope.toggleChildren = function(id, children) {
ToggleChildren({
scope: scope,
list: list,
id: id,
children: children
});
}
LoadBreadCrumbs();
if (scope.PostRefreshRemove) {
@ -76,7 +105,7 @@ function JobEventsList ($scope, $rootScope, $location, $log, $routeParams, Rest,
JobEventsList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'JobEventList',
'GenerateList', 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
'ProcessErrors','GetBasePath', 'LookUpInit'
'ProcessErrors','GetBasePath', 'LookUpInit', 'ToggleChildren'
];
function JobEventsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, JobEventForm,

View File

@ -0,0 +1,112 @@
/*********************************************
* Copyright (c) 2013 AnsibleWorks, Inc.
*
* ChildrenHelper
*
* Used in job_events to expand/collapse children
*
*/
angular.module('ChildrenHelper', ['RestServices', 'Utilities'])
.factory('ToggleChildren', ['Alert', 'Rest', 'GetBasePath','ProcessErrors',
function(Alert, Rest, GetBasePath, ProcessErrors) {
return function(params) {
var scope = params.scope;
var list = params.list;
var id = params.id;
var children = params.children;
var set = scope[list.name]; // set is now a pointer to scope[list.name]
function calcSpaces(lvl) {
return lvl * 24;
}
// Scan the array list and find the clicked element
var clicked;
var found = false;
for (var i = 0; i < set.length && found == false; i++){
if (set[i].id == id) {
clicked = i;
found = true;
}
}
// Expand or collapse children based on clicked element's icon
if (set[clicked]['ngicon'] == 'icon-expand-alt') {
// Expand: lookup and display children
Rest.setUrl(children);
Rest.get()
.success( function(data, status, headers, config) {
var found = false;
var level = (set[clicked].level !== undefined) ? set[clicked].level + 1 : 1;
var spaces = calcSpaces(level);
set[clicked]['ngicon'] = 'icon-collapse-alt';
for (var j=0; j < data.results.length; j++) {
data.results[j].level = level;
data.results[j].spaces = spaces;
//if (data.results[j].related.children === undefined) {
// data.results[j].spaces += 12
//}
data.results[j].event_display = data.results[j].event_display.replace(/^\u00a0*/g,'');
if (data.results[j].related.children) {
data.results[j]['ngclick'] = "toggleChildren(" + data.results[j].id + ", \"" + data.results[j].related.children + "\")";
data.results[j]['ngicon'] = 'icon-expand-alt';
}
if (clicked == (set.length - 1)) {
set.push(data.results[j]);
}
else {
set.splice(clicked + 1, 0, data.results[j]);
}
clicked++;
}
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Call to ' + children + ' failed. GET returned status: ' + status });
});
}
else {
// Collapse: find and remove children
var parents = [];
function findChildren(parent, idx) {
// recursive look through the tree finding all
// parents including and related the clicked element
for (var i=idx; i < set.length; i++) {
if (set[i].parent == parent) {
parents.push(parent);
findChildren(set[i].id, i + 1);
}
}
}
findChildren(id, clicked + 1);
// Remove all the children of the clicked element
var count;
for (var i=0; i < parents.length; i++) {
count = 0;
for (var j=clicked + 1; j< set.length; j++) {
if (set[j].parent == parents[i]) {
set.splice(j,1);
j=clicked; // start back a the top of the list
}
}
}
set[clicked]['ngicon'] = 'icon-expand-alt';
}
}
}]);

View File

@ -102,15 +102,15 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper'])
(list.fields[scope[iterator + 'SearchField']].searchType && list.fields[scope[iterator + 'SearchField']].searchType == 'gtzero') ) {
if (list.fields[scope[iterator + 'SearchField']].searchField) {
scope[iterator + 'SearchParams'] = '?' + list.fields[scope[iterator + 'SearchField']].searchField + '__';
scope[iterator + 'SearchParams'] = list.fields[scope[iterator + 'SearchField']].searchField + '__';
}
else if (list.fields[scope[iterator + 'SearchField']].sourceModel) {
// handle fields whose source is a related model e.g. inventories.organization
scope[iterator + 'SearchParams'] = '?' + list.fields[scope[iterator + 'SearchField']].sourceModel + '__' +
scope[iterator + 'SearchParams'] = list.fields[scope[iterator + 'SearchField']].sourceModel + '__' +
list.fields[scope[iterator + 'SearchField']].sourceField + '__';
}
else {
scope[iterator + 'SearchParams'] = '?' + scope[iterator + 'SearchField'] + '__';
scope[iterator + 'SearchParams'] = scope[iterator + 'SearchField'] + '__';
}
if ( list.fields[scope[iterator + 'SearchField']].searchType &&
@ -138,11 +138,16 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper'])
scope[iterator + 'SearchParams'] += (sort_order) ? '&order_by=' + escape(sort_order) : '';
}
else {
scope[iterator + 'SearchParams'] = '';
scope[iterator + 'SearchParams'] += (sort_order) ? '?order_by=' + escape(sort_order) : '';
scope[iterator + 'SearchParams'] = (sort_order) ? 'order_by=' + escape(sort_order) : "";
}
scope[iterator + 'Page'] = 0;
url += scope[iterator + 'SearchParams'];
if (/\/$/.test(url)) {
url += '?' + scope[iterator + 'SearchParams'];
}
else {
url += '&' + scope[iterator + 'SearchParams'];
}
url = url.replace(/\&\&/,'&');
url += (scope[iterator + 'PageSize']) ? '&page_size=' + scope[iterator + 'PageSize'] : "";
Refresh({ scope: scope, set: set, iterator: iterator, url: url });
}

View File

@ -15,20 +15,19 @@ angular.module('JobEventsListDefinition', [])
editTitle: 'Job Events',
index: false,
hover: true,
hasChildren: true,
fields: {
id: {
label: 'Event ID',
created: {
label: 'Creation Date',
key: true,
desc: true,
searchType: 'int'
nosort: true
},
event_display: {
label: 'Event',
link: true
},
created: {
label: 'Creation Date'
hasChildren: true,
link: true,
nosort: true
},
host: {
label: 'Host',
@ -36,7 +35,8 @@ angular.module('JobEventsListDefinition', [])
ngBind: 'jobevent.host_name',
sourceModel: 'host',
sourceField: 'name',
searchField: 'hosts__name'
searchField: 'hosts__name',
nosort: true
},
status: {
label: 'Status',
@ -44,7 +44,8 @@ angular.module('JobEventsListDefinition', [])
"class": 'job-\{\{ jobevent.status \}\}',
searchField: 'failed',
searchType: 'boolean',
searchOptions: [{ name: "success", value: 0 }, { name: "error", value: 1 }]
searchOptions: [{ name: "success", value: 0 }, { name: "error", value: 1 }],
nosort: true
}
},

View File

@ -84,7 +84,7 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers'])
var html = '';
html += "<td ";
html += "<td class=\"" + fld + "-column";
html += "<td class=\"" + fld + "-column";
html += (field['class']) ? " " + field['class'] : "";
html += "\" ";
html += (field.ngClass) ? this.attr(field, 'ngClass') : "";
@ -92,6 +92,13 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers'])
// Add ngShow
html += (field.ngShow) ? "<span " + Attr(field,'ngShow') + ">" : "";
// Add collapse/expand icon --used on job_events page
if (list['hasChildren'] && field.hasChildren) {
html += "<span style=\"padding-left: \{\{ " + list.iterator + ".spaces \}\}px\"><a href=\"\" ng-click=\"\{\{ " + list.iterator + ".ngclick \}\}\"> " +
"<i class=\"\{\{ " + list.iterator + ".ngicon \}\}\" ng-show=\"'\{\{ " +
list.iterator + ".related.children \}\}' !== ''\" ></i></a> ";
}
// Start the Link
if ((field.key || field.link || field.linkTo || field.ngClick ) && options['mode'] != 'lookup' && options['mode'] != 'select') {
@ -111,7 +118,9 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers'])
html += "<i ng-show=\"" + field.ngShowIcon + "\" class=\"" + field.icon + "\"></i> ";
}
else {
html += Icon(field.icon) + " ";
if (field.icon) {
html += Icon(field.icon) + " ";
}
}
// Add data binds
@ -120,15 +129,19 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers'])
html += "{{ " + field.ngBind + " }}";
}
else {
html += "{{" + list.iterator + "." + fld + "}}";
html += "{{" + list.iterator + "." + fld + "}}";
}
}
// Add additional text:
if (field.text) {
html += field.text;
}
if (list['hasChildren'] && field.hasChildren) {
html += "</span>";
}
// close the link
if ((field.key || field.link || field.linkTo || field.ngClick )
&& options.mode != 'lookup' && options.mode != 'select') {

View File

@ -153,20 +153,25 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
html += "<th>#</th>\n";
}
for (var fld in list.fields) {
html += "<th class=\"list-header\" id=\"" + fld + "-header\" ng-click=\"sort('" + fld + "')\">" + list.fields[fld].label;
html += " <i class=\"";
if (list.fields[fld].key) {
if (list.fields[fld].desc) {
html += "icon-sort-down";
html += "<th class=\"list-header\" id=\"" + fld + "-header\" ";
html += (list.fields[fld].nosort === undefined || list.fields[fld].nosort !== true) ? "ng-click=\"sort('" + fld + "')\"" : "";
html += ">" + list.fields[fld].label;
if (list.fields[fld].nosort === undefined || list.fields[fld].nosort !== true) {
html += " <i class=\"";
if (list.fields[fld].key) {
if (list.fields[fld].desc) {
html += "icon-sort-down";
}
else {
html += "icon-sort-up";
}
}
else {
html += "icon-sort-up";
else {
html += "icon-sort";
}
html += "\"></i></a>";
}
else {
html += "icon-sort";
}
html += "\"></i></a></th>\n";
html += "</th>\n";
}
if (options.mode == 'select') {
html += "<th>Select</th>";

View File

@ -74,6 +74,7 @@
<script src="{{ STATIC_URL }}js/helpers/Groups.js"></script>
<script src="{{ STATIC_URL }}js/helpers/Hosts.js"></script>
<script src="{{ STATIC_URL }}js/helpers/Parse.js"></script>
<script src="{{ STATIC_URL }}js/helpers/Children.js"></script>
<script src="{{ STATIC_URL }}lib/ansible/directives.js"></script>
<script src="{{ STATIC_URL }}lib/ansible/filters.js"></script>
<script src="{{ STATIC_URL }}lib/ansible/api-loader.js"></script>