// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you 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. /** * Create dynamic list view based on data callbacks */ (function($, cloudStack, _l, _s) { var uiActions = { standard: function($instanceRow, args, additional) { var isAddAction = args.action.isAdd; var listViewArgs = $instanceRow.closest('div.list-view').data('view-args'); var notification = args.action.notification ? args.action.notification : {}; var messages = args.action ? args.action.messages : {}; var preAction = args.action ? args.action.preAction : {}; var action = args.action ? args.action.action : {}; var multiSelect = args.action.isMultiSelectAction; var needsRefresh = args.action.needsRefresh; var section; var data; var messageArgs; if (multiSelect) { data = { id: $.map($instanceRow, function(elem) { return $(elem).data('list-view-item-id'); }), jsonObj: $.map($instanceRow, function(elem) { return $(elem).data('jsonObj'); }) }; messageArgs = { name: $.map($instanceRow, function(elem) { return $(elem).find('td.name span').html() }) }; } else { data = { id: $instanceRow.data('list-view-item-id'), jsonObj: $instanceRow.data('jsonObj') }; messageArgs = { name: $instanceRow.find('td.name span').html() }; } var $listView = $instanceRow.closest('.list-view'); if (args.data) $.extend(true, data, args.data); if (listViewArgs) section = listViewArgs.section; notification.desc = messages ? messages.notification(messageArgs) : null; if (listViewArgs) notification.section = listViewArgs.id; // Handle pre-action (occurs before any other behavior happens) if (preAction) { var preActionContext = $.extend(true, {}, listViewArgs.context); preActionContext[ listViewArgs.activeSection ] = (multiSelect ? data.jsonObj : [data.jsonObj]); if (!preAction({ context: preActionContext })) return false; } var performAction = function(data, options) { if (!options) options = {}; var $form = options.$form; var isHeader = options.isHeader; $instanceRow = options.$item ? options.$item : $instanceRow; var $item = options.$item; var context = $.extend(true, {}, listViewArgs.context); context[ listViewArgs.activeSection ] = (multiSelect ? $.map($instanceRow, function(elem) { return $(elem).data('jsonObj'); }) : [$instanceRow.data('jsonObj')]); // Make sure the master checkbox is unselected if (multiSelect) { var $listView2 = $instanceRow.closest('.list-view'); $listView2.find('input.multiSelectMasterCheckbox').prop('checked', false); toggleMultiSelectActions($listView2, false); } var externalLinkAction = action.externalLink; if (externalLinkAction) { // Show popup immediately, do not proceed through normal action process window.open( // URL externalLinkAction.url({ context: context }), // Title externalLinkAction.title({ context: context }), // Window options 'menubar=0,resizable=0,' + 'width=' + externalLinkAction.width + ',' + 'height=' + externalLinkAction.height ); } else if (action.custom && !action.noAdd) { action.custom({ data: data, ref: options.ref, context: context, $instanceRow: $instanceRow, complete: function(args) { args = args ? args : {}; var $item = args.$item; notification.desc = messages.notification(args.messageArgs); notification._custom = args._custom; cloudStack.ui.notifications.add( notification, function(args) { if (listViewArgs.onActionComplete) { listViewArgs.onActionComplete(); } if ($item.is(':visible') && !isHeader) { replaceItem( $item, args.data, args.actionFilter ? args.actionFilter : $instanceRow.next().data('list-view-action-filter') ); } }, {}, // Error function(args) { if (args && args.updatedData) { if ($item.is(':visible') && !isHeader) { replaceItem( $item, args.updatedData, args.actionFilter ); } } else { $item.remove(); } } ); } }); } else if (action.uiCustom) { action.uiCustom({ $item: $instanceRow }); } else { if (needsRefresh) { var $loading = $('
').addClass('loading-overlay'); if ($listView) { $listView.prepend($loading); } else { $instanceRow.closest('.list-view').prepend($loading) } } var actionArgs = { data: data, ref: options.ref, context: options.context, $form: $form, response: { success: function(args) { args = args ? args : {}; var $prevRow, $newRow; // Make copy of previous row, in case data is needed $prevRow = $instanceRow.clone(); if (multiSelect) { $prevRow.find('.quick-view').addClass('loading-overlay'); $.each($prevRow, function(index, elem) { $(elem).data($($instanceRow[index]).data()); }); } else { $prevRow.data($instanceRow.data()); } // Set loading appearance if (args.data && (!isHeader || multiSelect)) { if (multiSelect) { $instanceRow = $.map($instanceRow, function(elem, index) { return replaceItem( $(elem), $.extend($(elem).data('json-obj'), args.data[index]), $(elem).data('list-view-action-filter') )[0]; }); } else { $instanceRow = replaceItem( $instanceRow, $.extend($instanceRow.data('json-obj'), args.data), $instanceRow.data('list-view-action-filter') ); } } if (multiSelect) { $.each($instanceRow, function(index, elem) { $(elem).find('td:last').children().remove(); $(elem).find('td:last').append($('
').addClass('loading')); $(elem).addClass('loading'); if (options.$item) $(elem).data('list-view-new-item', true); // Disable any clicking/actions for row $(elem).bind('click', function() { return false; }); }); } else { $instanceRow.find('td:last').children().remove(); $instanceRow.find('td:last').append($('
').addClass('loading')); $instanceRow.addClass('loading'); if (options.$item) $instanceRow.data('list-view-new-item', true); // Disable any clicking/actions for row $instanceRow.bind('click', function() { return false; }); } if(args.notification) notification = args.notification; notification._custom = args._custom; if (additional && additional.success) additional.success(args); if (listViewArgs.onActionComplete == true) { listViewArgs.onActionComplete(); } cloudStack.ui.notifications.add( notification, // Success function(args) { if (!args) args = {}; var actionFilter = args.actionFilter ? args.actionFilter : (multiSelect ? $.map($instanceRow, function(elem) { $(elem).data('list-view-action-filter') }) : $instanceRow.data('list-view-action-filter')); if (!isHeader || multiSelect) { var visible = (multiSelect ? $($instanceRow[0]).is(':visible') : $instanceRow.is(':visible')); if (visible) { if (args.data) { if (multiSelect) { $newRow = []; $.each($instanceRow, function(index, elem) { $newRow.push( replaceItem($(elem), args.data, //$.extend($(elem).data('json-obj'), args.data[index]), actionFilter) ); }); } else { $newRow = replaceItem($instanceRow, args.data, actionFilter); } } else { // Nothing new, so just put in existing data if (multiSelect) { $instanceRow = $.map($instanceRow, function(elem) { replaceItem($(elem), $(elem).data('json-obj'), actionFilter)[0] }); } else { $newRow = replaceItem($instanceRow, $instanceRow.data('json-obj'), actionFilter); } } if (needsRefresh) { if ($listView.closest('.detail-view').length) { $('.detail-view:last .button.refresh').click(); } else { $loading.remove(); $listView.listView('refresh'); } } } if (additional && additional.complete) additional.complete(args, $newRow); } if (messages.complete) { cloudStack.dialog.notice({ message: messages.complete(args.data) }); } if (options.complete) { options.complete(args); } if (listViewArgs.onActionComplete) { listViewArgs.onActionComplete(); } }, {}, // Error function(errorArgs) { if (!isHeader) { if (isAddAction == true && $instanceRow.data('list-view-new-item')) { // For create forms $instanceRow.remove(); } else { // For standard actions if(!args.notification) { if (multiSelect) { $.each($instanceRow, function(index, elem) { replaceItem( $(elem), $.extend($(elem).data('json-obj'), errorArgs.data), errorArgs.actionFilter ? errorArgs.actionFilter : $(elem).data('list-view-action-filter') ); }); } else { replaceItem( $instanceRow, $.extend($instanceRow.data('json-obj'), errorArgs.data), errorArgs.actionFilter ? errorArgs.actionFilter : $instanceRow.data('list-view-action-filter') ); } } } } if (options.error) { options.error(errorArgs); } } ); }, error: function(message) { $instanceRow.removeClass('loading'); $instanceRow.find('td.quick-view').removeClass('loading-overlay'); if (!isHeader) { if (($.isPlainObject(args.action.createForm) && args.action.addRow != 'false') || (!args.action.createForm && args.action.addRow == 'true')) { $instanceRow.remove(); } } if (needsRefresh) { if (!$listView.closest('.detail-view').length) { $loading.remove(); } } if (options.error) options.error(message); if (message) cloudStack.dialog.notice({ message: message }); } } }; if (action.custom && action.noAdd) { action.custom({ data: data, ref: options.ref, context: context, $instanceRow: $instanceRow, complete: actionArgs.response.success }); } else { action(actionArgs); } } }; var context = $.extend({}, listViewArgs.context); context[ listViewArgs.activeSection ] = (multiSelect ? $.map($instanceRow, function(elem) { return $(elem).data('jsonObj'); }) : [$instanceRow.data('jsonObj')]); messageArgs.context = context; if (!args.action.action.externalLink && !args.action.createForm && args.action.addRow != 'true' && !action.custom && !action.uiCustom && !args.action.listView) { cloudStack.dialog.confirm({ message: messages.confirm(messageArgs), action: function() { performAction(data, { context: context, isMultiSelectAction: multiSelect, isHeader: args.action.isHeader }); } }); } else if (action.custom || action.uiCustom) { performAction(); } else if (args.action.listView) { cloudStack.dialog.listView({ context: context, listView: args.action.listView, after: function(args) { performAction(null, { context: args.context }); } }); } else { var addRow = args.action.addRow == "false" ? false : true; var isHeader = args.action.isHeader; var createFormContext = $.extend({}, context); var externalLinkAction = action.externalLink; if (externalLinkAction) { // Show popup immediately, do not proceed through normal action process window.open( // URL externalLinkAction.url({ context: context }), // Title externalLinkAction.title({ context: context }), // Window options 'menubar=0,resizable=0,' + 'width=' + externalLinkAction.width + ',' + 'height=' + externalLinkAction.height ); } else if (args.action.createForm) { cloudStack.dialog.createForm({ form: args.action.createForm, after: function(args) { var $newItem; if (!isHeader) { if (addRow != false) { $newItem = $listView.listView('prependItem', { data: [ $.extend(args.data, { state: 'Creating', status: 'state.Creating', allocationstate: 'Creating' }) ] }); } else { $newItem = $instanceRow; } performAction(args.data, { ref: args.ref, context: createFormContext, $item: $newItem, $form: args.$form }); } else { var $loading = $('
').addClass('loading-overlay'); $loading.appendTo($listView); performAction(args.data, { ref: args.ref, context: createFormContext, $form: args.$form, isHeader: isHeader, complete: function(args) { $loading.remove(); $listView.listView('refresh'); }, error: function(args) { $loading.remove(); } }); } }, ref: listViewArgs.ref, context: createFormContext }); } else { cloudStack.dialog.confirm({ message: messages.confirm(messageArgs), action: function() { var $newItem; if (addRow && !action.isHeader) { $newItem = $listView.listView('prependItem', { data: [ $.extend(args.data, { state: 'Creating', status: 'state.Creating', allocationstate: 'Creating' }) ] }); } else if (action.isHeader) { $newItem = $('
'); } else { $newItem = $instanceRow; } performAction(args.data, { ref: args.ref, context: createFormContext, $item: $newItem, $form: args.$form }); } }); } } }, remove: function($instanceRow, args) { uiActions.standard($instanceRow, args, { complete: function(args, $newRow) { $newRow.remove(); } }); }, edit: function($instanceRow, args) { var $td = $instanceRow.find('td.editable'); var $edit = $td.find('div.edit'); var $editInput = $edit.find('input'); var $label = $td.find('span'); var $listView = $instanceRow.closest('.list-view'); var listViewArgs = $listView.data('view-args'); // Hide label, show edit field var showEditField = function() { $edit.css({ opacity: 1 }); $label.fadeOut('fast', function() { $edit.fadeIn(); $editInput.focus(); $instanceRow.closest('div.data-table').dataTable('refresh'); }); }; // Hide edit field, validate and save changes var showLabel = function(val, options) { if (!options) options = {}; var oldVal = $label.html(); if (val != null) $label.html(_s(val)); var data = { id: $instanceRow.data('list-view-item-id'), jsonObj: $instanceRow.data('jsonObj') }; data[$td.data('list-view-item-field')] = $editInput.val(); var context = $.extend({}, listViewArgs.context); context[ listViewArgs.activeSection ] = $instanceRow.data('jsonObj'); args.callback({ data: data, context: context, response: { success: function(args) { $edit.hide(); $label.fadeIn(); $instanceRow.closest('div.data-table').dataTable('refresh'); if (options.success) options.success(args); }, error: function(message) { if (message) { cloudStack.dialog.notice({ message: message }); $edit.hide(), $label.html(_s(oldVal)).fadeIn(); $instanceRow.closest('div.data-table').dataTable('refresh'); if (options.error) options.error(args); } } } }); }; if (args.cancel) { //click Cancel button // showLabel(); var oldVal = $label.html(); $edit.hide(); $label.fadeIn(); $instanceRow.closest('div.data-table').dataTable('refresh'); $editInput.val(_s(oldVal)); return false; } if (!$editInput.is(':visible') || !(typeof(args.action) == 'undefined')) { //click Edit button showEditField(); } else if ($editInput.val() != $label.html()) { //click Save button with changed value if ($editInput.val().match(/<|>/)) { cloudStack.dialog.notice({ message: 'message.validate.invalid.characters' }); return false; } $edit.animate({ opacity: 0.5 }); var originalName = $label.html(); var newName = $editInput.val(); showLabel(newName, { success: function() { cloudStack.ui.notifications.add({ section: $instanceRow.closest('div.view').data('view-args').id, desc: newName ? _l('Set value of') + ' ' + $instanceRow.find('td.name span').html() + ' ' + _l('to') + ' ' + _s(newName) : _l('Unset value for') + ' ' + $instanceRow.find('td.name span').html() }, function(args) {}, [{ name: newName }] ); } }); } else { //click Save button with unchanged value showLabel(); } return $instanceRow; } }; var rowActions = { _std: function($tr, action) { action(); $tr.closest('.data-table').dataTable('refresh'); setTimeout(function() { $tr.closest('.data-table').dataTable('selectRow', $tr.index()); }, 0); }, moveTop: function($tr) { rowActions._std($tr, function() { $tr.closest('tbody').prepend($tr); $tr.closest('.list-view').animate({ scrollTop: 0 }); }); }, moveBottom: function($tr) { rowActions._std($tr, function() { $tr.closest('tbody').append($tr); $tr.closest('.list-view').animate({ scrollTop: 0 }); }); }, moveUp: function($tr) { rowActions._std($tr, function() { $tr.prev().before($tr); }); }, moveDown: function($tr) { rowActions._std($tr, function() { $tr.next().after($tr); }); }, moveTo: function($tr, index, after) { rowActions._std($tr, function() { var $target = $tr.closest('tbody').find('tr').filter(function() { return $(this).index() == index; }); // if ($target.index() > $tr.index()) $target.after($tr); // else $target.before($tr); $tr.closest('.list-view').scrollTop($tr.position().top - $tr.height() * 2); if (after) setTimeout(function() { after(); }); }); } }; /** * Edit field text * * @param $td {jQuery} to put input field into */ var createEditField = function($td) { $td.addClass('editable'); // Put label into a span var sanitizedValue = $td.html(); $('').html(sanitizedValue).appendTo($td.html('')); var $editArea = $('
').addClass('edit'); var $editField = $('').addClass('edit').attr({ type: 'text', value: cloudStack.sanitizeReverse(sanitizedValue) }); var $actionButton = $('
').addClass('action'); var $saveButton = $actionButton.clone().addClass('save').attr({ 'title': _l('Save') }); var $cancelButton = $actionButton.clone().addClass('cancel').attr({ 'title': _l('Cancel edit') }); $([$editField, $saveButton, $cancelButton]).each(function() { this.appendTo($editArea); }); return $editArea.hide(); }; var renderActionCol = function(actions) { return $.grep( $.map(actions, function(value, key) { return key; }), function(elem) { return elem != 'add'; } ).length; }; var createHeader = function(preFilter, fields, $table, actions, options) { if (!options) options = {}; var $tr = $(''); var $thead = $('').prependTo($table).append($tr); var reorder = options.reorder; var detailView = options.detailView; var multiSelect = options.multiSelect; var groupableColumns = options.groupableColumns; var viewArgs = $table.closest('.list-view').data('view-args'); var uiCustom = viewArgs.uiCustom; var hiddenFields = []; if (preFilter != null) hiddenFields = preFilter(); var addColumnToTr = function($tr, key, colspan, label, needsCollapsibleColumn) { var trText = _l(label); var $th = $('').addClass(key).attr('colspan', colspan).attr('title', trText).appendTo($tr); if ($th.index()) $th.addClass('reduced-hide'); $th.css({'border-right': '1px solid #C6C3C3', 'border-left': '1px solid #C6C3C3'}); if (needsCollapsibleColumn) { var karetLeft = $('').css({'margin-right': '10px'}); karetLeft.attr('title', trText); karetLeft.appendTo($th); $('').html('«').css({'font-size': '15px', 'float': 'right'}).appendTo(karetLeft); $('').html(trText).appendTo(karetLeft); $th.click(function(event) { event.stopPropagation(); var $th = $(this); var startIndex = 0; $th.prevAll('th').each(function() { startIndex += parseInt($(this).attr('colspan')); }); var endIndex = startIndex + parseInt($th.attr('colspan')); // Hide Column group $th.hide(); $th.closest('table').find('tbody td').filter(function() { return $(this).index() >= startIndex && $(this).index() < endIndex; }).hide(); $th.closest('table').find('thead tr:last th').filter(function() { return $(this).index() >= startIndex && $(this).index() < endIndex; }).hide(); // Show collapsible column with blank cells $th.next('th').show(); $th.closest('table').find('tbody td').filter(function() { return $(this).index() == endIndex; }).show(); $th.closest('table').find('thead tr:last th').filter(function() { return $(this).index() == endIndex; }).show(); // Refresh list view $tr.closest('.list-view').find('.no-split').dataTable('refresh'); }); var karetRight = addColumnToTr($tr, 'collapsible-column', 1, ''); $('').html(trText.substring(0,3)).appendTo(karetRight); $('').css({'font-size': '15px'}).html(' »').appendTo(karetRight); karetRight.attr('title', trText); karetRight.css({'border-right': '1px solid #C6C3C3', 'border-left': '1px solid #C6C3C3', 'min-width': '10px', 'width': '10px', 'max-width': '45px', 'padding': '2px'}); karetRight.hide(); karetRight.click(function(event) { event.stopPropagation(); var prevTh = $(this).prev('th'); var startIndex = 0; prevTh.prevAll('th').each(function() { startIndex += parseInt($(this).attr('colspan')); }); var endIndex = startIndex + parseInt(prevTh.attr('colspan')); prevTh.show(); prevTh.closest('table').find('tbody td').filter(function() { return $(this).index() >= startIndex && $(this).index() < endIndex; }).show(); prevTh.closest('table').find('thead tr:last th').filter(function() { return $(this).index() >= startIndex && $(this).index() < endIndex; }).show(); prevTh.next('th').hide(); prevTh.closest('table').find('tbody td').filter(function() { return $(this).index() == endIndex; }).hide(); prevTh.closest('table').find('thead tr:last th').filter(function() { return $(this).index() == endIndex; }).hide(); $tr.closest('.list-view').find('.no-split').dataTable('refresh'); }); } else { $th.html(trText); } return $th; }; if (groupableColumns) { $tr.addClass('groupable-header-columns').addClass('groupable-header'); $.each(fields, function(key) { if ($.inArray(key, hiddenFields) != -1) { return true; } var field = this; if (field.columns) { var colspan = Object.keys(field.columns).length; addColumnToTr($tr, key, colspan, field.label, true); } else { var label = ''; if (key == 'name') { label = 'label.resources'; } addColumnToTr($tr, key, 1, label); } return true; }); if (detailView && !$.isFunction(detailView) && !detailView.noCompact && !uiCustom) { addColumnToTr($tr, 'quick-view', 1, ''); } $tr = $('').appendTo($thead); $tr.addClass('groupable-header'); } if (multiSelect) { var $th = $('').addClass('multiselect').appendTo($tr); var $multiSelectMaster = $('') .attr('type', 'checkbox') .addClass('multiSelectMasterCheckbox'); $multiSelectMaster.appendTo($th); $multiSelectMaster.click(function() { var isMasterChecked = $(this).prop('checked'); $('.multiSelectCheckbox').prop('checked', isMasterChecked); toggleMultiSelectActions($table.closest('.list-view'), isMasterChecked); }); } $.each(fields, function(key) { if ($.inArray(key, hiddenFields) != -1) return true; var field = this; if (field.columns) { $.each(field.columns, function(idx) { var subfield = this; addColumnToTr($tr, key, 1, subfield.label); return true; }); var blankCell = addColumnToTr($tr, 'collapsible-column', 1, ''); blankCell.css({'min-width': '10px', 'width': '10px'}); blankCell.hide(); } else { addColumnToTr($tr, key, 1, field.label); } return true; }); // Re-order row buttons if (reorder) { $tr.append( $('').html(_l('label.order')).addClass('reorder-actions reduced-hide') ); } // Actions column var actionsArray = actions ? $.map(actions, function(v, k) { if (k == 'add' || k == 'rootAdminAddGuestNetwork') { v.isAdd = true; } return v; }) : []; var headerActionsArray = $.grep( actionsArray, function(action) { return action.isHeader || action.isAdd; } ); if (actions && !options.noActionCol && renderActionCol(actions) && actionsArray.length != headerActionsArray.length) { $tr.append( $('') .html(_l('label.actions')) .addClass('actions reduced-hide') ); } // Quick view if (detailView && !$.isFunction(detailView) && !detailView.noCompact && !uiCustom) { $tr.append( $('') .html(_l('label.quickview')) .addClass('quick-view reduced-hide') ); } return $thead; }; var createFilters = function($toolbar, filters) { if (!filters) return false; var $filters = $('
').addClass('filters reduced-hide'); $filters.append($('