// 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 needsRefresh = args.action.needsRefresh; var section; var data = { id: $instanceRow.data('list-view-item-id'), jsonObj: $instanceRow.data('jsonObj') }; var $listView = $instanceRow.closest('.list-view'); var messageArgs = { name: $instanceRow.find('td.name span').html() }; 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 ] = [$instanceRow.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 ] = [$instanceRow.data('jsonObj')]; 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'); $listView.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(); $prevRow.data($instanceRow.data()); // Set loading appearance if (args.data && !isHeader) { $instanceRow = replaceItem( $instanceRow, $.extend($instanceRow.data('json-obj'), args.data), $instanceRow.data('list-view-action-filter') ); } $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 : $instanceRow.data('list-view-action-filter'); if (!isHeader) { if ($instanceRow.is(':visible')) { if (args.data) { $newRow = replaceItem($instanceRow, $.extend($instanceRow.data('json-obj'), args.data), actionFilter); } else { // Nothing new, so just put in existing data $newRow = replaceItem($instanceRow, $instanceRow.data('json-obj'), actionFilter); } if (needsRefresh) { if ($listView.closest('.detail-view').size()) { $('.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) { 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) { if (!isHeader) { if (($.isPlainObject(args.action.createForm) && args.action.addRow != 'false') || (!args.action.createForm && args.action.addRow == 'true')) { $instanceRow.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 ] = [$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({ id: $instanceRow.data('list-view-item-id') }, { context: context }); } }); } 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: '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: '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 $thead = $('').prependTo($table).append($('')); var reorder = options.reorder; var detailView = options.detailView; var viewArgs = $table.closest('.list-view').data('view-args'); var uiCustom = viewArgs.uiCustom; var hiddenFields = []; if (preFilter != null) hiddenFields = preFilter(); $.each(fields, function(key) { if ($.inArray(key, hiddenFields) != -1) return true; var field = this; var $th = $('').addClass(key).appendTo($thead.find('tr')); if ($th.index()) $th.addClass('reduced-hide'); $th.html(_l(field.label)); return true; }); // Re-order row buttons if (reorder) { $thead.find('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 && renderActionCol(actions) && actionsArray.length != headerActionsArray.length) { $thead.find('tr').append( $('') .html(_l('label.actions')) .addClass('actions reduced-hide') ); } // Quick view if (detailView && !$.isFunction(detailView) && !detailView.noCompact && !uiCustom) { $thead.find('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($('