blob: cca70630b2fa817057642051655ccfebc0c60218 [file] [log] [blame]
// 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.
(function($, cloudStack, _l) {
var replaceListViewItem = function($detailView, newData, options) {
var $row = $detailView ? $detailView.data('list-view-row') :
options.$row;
if (!$row) return;
var $listView = $row.closest('.list-view');
if (!$listView.parents('html').length) return;
var $newRow;
var jsonObj = $row.data('json-obj');
if ($listView.length > 0) { //$listView.length is 0 after calling $(window).trigger('cloudStack.fullRefresh')
$listView.listView('replaceItem', {
$row: $row,
data: $.extend(jsonObj, newData),
after: function($newRow) {
if ($detailView) {
$detailView.data('list-view-row', $newRow);
}
setTimeout(function() {
$('.data-table').dataTable('selectRow', $newRow.index());
}, 100);
}
});
}
// Refresh detail view context
if ($detailView) {
var detailViewArgs = $detailView.data('view-args');
var listViewArgs = $listView.data('view-args');
var contextID = listViewArgs.sections && listViewArgs.sections[detailViewArgs.section].id ?
listViewArgs.sections[detailViewArgs.section].id :
detailViewArgs.section;
$.extend($detailView.data('view-args').context[contextID][0], newData);
}
};
var tabEvents = {
create: function(event, ui) {
manageTabsContent(event, ui);
},
activate: function(event, ui) {
manageTabsContent(event, ui);
}
};
/**
* Available UI actions to perform for buttons
*/
var uiActions = {
/**
* Default behavior for actions -- just show a confirmation popup and add notification
*/
standard: function($detailView, args, additional) {
var tab = args.tabs[args.activeTab];
var isMultiple = tab.multiple;
var action = isMultiple ? tab.actions[args.actionName] : args.actions[args.actionName];
var preAction = action.preAction;
var notification = action.notification ?
action.notification : {};
var messages = action.messages;
var id = args.id;
var jsonObj = args.jsonObj;
var context = $.extend(true, {},
args.context ? args.context : $detailView.data('view-args').context);
var _custom = $detailView.data('_custom');
var customAction = action.action.custom;
var noAdd = action.noAdd;
var noRefresh = additional.noRefresh;
var messageArgs = {
name: $detailView.find('tr.name td.value').html(),
context: context
};
// Handle pre-action (occurs before any other behavior happens)
if (preAction) {
if (!preAction({
context: context
})) return false;
}
var updateTabContent = function(newData) {
var $detailViewElems = $detailView.find('ul.ui-tabs-nav, .detail-group').remove();
var viewArgs = $detailView.data('view-args');
var context = viewArgs.context;
var activeContextItem = viewArgs.section && context[viewArgs.section] ?
context[viewArgs.section][0] : null;
$detailView.tabs('destroy');
$detailView.data('view-args').jsonObj = newData;
if (activeContextItem) {
$.extend(activeContextItem, newData);
}
makeTabs(
$detailView,
$detailView.data('view-args').tabs, {
context: context,
tabFilter: $detailView.data('view-args').tabFilter,
newData: newData
}
).appendTo($detailView);
$detailView.tabs(tabEvents);
};
var performAction = function(data, options) {
if (!options) options = {};
var $form = options.$form;
var viewArgs = $detailView.data('view-args');
var $loading = $('<div>').addClass('loading-overlay');
var setLoadingState = function() {
if (viewArgs && viewArgs.onPerformAction) {
viewArgs.onPerformAction();
}
$detailView.addClass('detail-view-loading-state');
$detailView.prepend($loading);
};
if (customAction && !noAdd) {
customAction({
context: context,
$detailView: $detailView,
start: setLoadingState,
complete: function(args) {
if (!$detailView.hasClass('detail-view-loading-state')) {
setLoadingState();
}
args = args ? args : {};
var $item = args.$item;
var $row = $detailView.data('list-view-row');
var error = args.error;
notification.desc = messages.notification(args.messageArgs);
notification._custom = $.extend(args._custom ? args._custom : {}, {
$detailView: $detailView
});
if (error) {
notification.interval = 1;
notification.poll = function(args) {
cloudStack.dialog.notice({ message: error });
args.error(error);
}
}
cloudStack.ui.notifications.add(
notification,
// Success
function(args) {
if (viewArgs && viewArgs.onActionComplete) {
viewArgs.onActionComplete();
}
if (!$detailView.parents('html').length) {
replaceListViewItem(null, args.data, {
$row: $row
});
return;
}
replaceListViewItem($detailView, args.data);
$loading.remove();
$detailView.removeClass('detail-view-loading-state');
if (!noRefresh) {
updateTabContent(args.data);
}
},
{},
// Error
function(args) {
$loading.remove();
}
);
}
});
} else {
// Set loading appearance
var $loading = $('<div>').addClass('loading-overlay');
$detailView.prepend($loading);
action.action({
data: data,
_custom: _custom,
ref: options.ref,
context: context,
$form: $form,
response: {
success: function(args) {
args = args ? args : {};
notification._custom = $.extend(args._custom ? args._custom : {}, {
$detailView: $detailView
});
if (additional && additional.success) additional.success(args);
// Setup notification
cloudStack.ui.notifications.add(
notification,
function(args2) { //name parameter as "args2" instead of "args" to avoid override "args" from success: function(args) {
if ($detailView.parents('html').length) {
$loading.remove();
if (!noRefresh && !viewArgs.compact) {
if (isMultiple) {
$detailView.find('.refresh').click();
} else {
updateTabContent(args.data ? args.data : args2.data);
}
}
}
if (messages.complete) {
if( messages.complete(args2.data) != null && messages.complete(args2.data).length > 0) {
cloudStack.dialog.notice({
message: messages.complete(args2.data)
});
}
}
if (additional && additional.complete) additional.complete($.extend(true, args, {
$detailView: $detailView
}), args2);
replaceListViewItem($detailView, args.data ? args.data : args2.data);
if (viewArgs && viewArgs.onActionComplete) {
viewArgs.onActionComplete();
}
},
{},
// Error
function(args) {
$loading.remove();
}
);
return true;
},
error: function(args) { //args here is parsed errortext from API response
if (args != null & args.length > 0) {
cloudStack.dialog.notice({
message: args
});
}
// Quickview: Remove loading state on list row
if (viewArgs && viewArgs.$listViewRow) {
viewArgs.$listViewRow.removeClass('loading')
.find('.loading').removeClass('loading');
}
$loading.remove();
}
}
});
if (viewArgs && viewArgs.onPerformAction) {
viewArgs.onPerformAction();
}
}
};
var externalLinkAction = action.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=1,' + 'width=' + externalLinkAction.width + ',' + 'height=' + externalLinkAction.height
);
} else {
notification.desc = messages.notification(messageArgs);
notification.section = 'instances';
if (!action.createForm && !action.listView) {
if (messages && messages.confirm) {
cloudStack.dialog.confirm({
message: messages.confirm(messageArgs),
isWarning: messages.isWarning,
action: function() {
performAction({
id: id
});
}
});
} else {
performAction({
id: id
});
}
} else if (action.createForm) {
cloudStack.dialog.createForm({
form: action.createForm,
after: function(args) {
performAction(args.data, {
ref: args.ref,
context: context,
$form: args.$form
});
},
ref: {
id: id
},
context: context,
jsonObj: jsonObj
});
} else if (action.listView) {
cloudStack.dialog.listView({
context: context,
listView: action.listView,
after: function(args) {
context = args.context;
performAction();
}
});
}
}
},
remove: function($detailView, args) {
var tab = args.tabs[args.activeTab];
var isMultiple = tab.multiple;
uiActions.standard($detailView, args, {
noRefresh: true,
complete: function(args) {
if (isMultiple && $detailView.is(':visible')) {
$detailView.find('.refresh').click(); // Reload tab
} else {
var $browser = $('#browser .container');
var $panel = $detailView.closest('.panel');
if ($detailView.is(':visible')) {
$browser.cloudBrowser('selectPanel', {
panel: $panel.prev()
});
}
if ($detailView.data("list-view-row") != null) {
var $row = $detailView.data('list-view-row');
var $tbody = $row.closest('tbody');
$row.remove();
if (!$tbody.find('tr').length) {
$("<tr>").addClass('empty').append(
$("<td>").html(_l('label.no.data'))
).appendTo($tbody);
}
$tbody.closest('table').dataTable('refresh');
}
}
}
});
},
destroy: function($detailView, args) {
var tab = args.tabs[args.activeTab];
var isMultiple = tab.multiple;
uiActions.standard($detailView, args, {
noRefresh: true,
complete: function(args, args2) {
if ((!('id' in args2.data)) && ('toRemove' in args2.data) && (args2.data.toRemove == true)) {
if (isMultiple && $detailView.is(':visible')) {
$detailView.find('.refresh').click(); // Reload tab
} else {
var $browser = $('#browser .container');
var $panel = $detailView.closest('.panel');
if ($detailView.is(':visible')) {
$browser.cloudBrowser('selectPanel', {
panel: $panel.prev()
});
}
if ($detailView.data("list-view-row") != null) {
var $row = $detailView.data('list-view-row');
var $tbody = $row.closest('tbody');
$row.remove();
if (!$tbody.find('tr').length) {
$("<tr>").addClass('empty').append(
$("<td>").html(_l('label.no.data'))
).appendTo($tbody);
}
$tbody.closest('table').dataTable('refresh');
}
}
} else {
$detailView.find('.refresh').click(); // Reload tab
}
}
});
},
/**
* Convert editable fields to text boxes; clicking again saves data
*
* @param $detailView
* @param callback
*/
edit: function($detailView, args) {
$detailView.addClass('edit-mode');
var token_value = "";
if ($detailView.find('.button.done').length) return false;
// Convert value TDs
var $inputs = $detailView.find('input, select, textarea').filter(function() {
return !$(this).closest('.tagger').length && !$(this).attr('type') == 'submit';
});
var action = args.actions[args.actionName];
var id = $detailView.data('view-args').id;
var $editButton = $('<div>').addClass('button done').html(_l('label.apply')).hide();
var $cancelButton = $('<div>').addClass('button cancel').html(_l('label.cancel')).hide();
// Show buttons
$.merge($editButton, $cancelButton)
.appendTo(
$detailView.find('.ui-tabs-panel .detail-group.actions')
).fadeIn();
$detailView.find('.tagger').find('input[type=text]').val('');
$('div.container div.panel div.detail-group .details .main-groups').find('.cidr').toolTip({
docID: 'helpIPReservationCidr',
mode: 'hover',
tooltip: '.tooltip-box'
});
var removeCopyPasteIcons = function() {
$detailView.find('.copypasteactive').removeClass('copypasteactive').addClass('copypasteenabledvalue');
$detailView.find('td.value .copypasteicon').hide();
};
removeCopyPasteIcons();
var convertInputs = function($inputs) {
// Save and turn back into labels
var $token;
var tags_value = "";
$inputs.each(function() {
if ($(this).closest('.tagger').length) return true;
var $input = $(this);
var $value = $input.closest('td.value span');
if ($input.is('input[type=text]'))
{
if ($input.attr('name') === "token-input-")
{
$token = $value;
}
else if (($input.attr('name') === "tags") || ($input.attr('name') === "hosttags"))
{
tags_value = $input.attr('value');
}
$value.html(_s(
$input.attr('value')
));
}
else if ($input.is('input[type=password]')) {
$value.html('');
} else if ($input.is('input[type=checkbox]')) {
var val = $input.is(':checked');
$value.data('detail-view-boolean-value', _s(val));
$value.html(_s(val) ? _l('label.yes') : _l('label.no'));
} else if ($input.is('select')) {
$value.html(_s(
$input.find('option:selected').html()
));
$value.data('detail-view-selected-option', _s($input.find('option:selected').val()));
} else if ($input.is('textarea')) {
$value.html(_s(
$input.val()
));
$value.data('detail-view-editable-textarea', _s($input.find('option:selected').val()));
}
});
if ($token) {
$token.html(_s(tags_value));
}
};
var removeEditForm = function() {
$detailView.removeClass('edit-mode');
// Remove Edit form
var $form = $detailView.find('form').filter(function() {
return !$(this).closest('.tagger').length;
});
if ($form.length) {
var $mainGroups = $form.find('div.main-groups').detach();
$form.parent('div').append($mainGroups);
$form.remove();
}
//Remove required labels
$detailView.find('span.field-required').remove();
};
// Put in original values
var cancelEdits = function($inputs, $editButton) {
$inputs.each(function() {
if ($(this).closest('.tagger').length) return true;
var $input = $(this);
var $value = $input.closest('td.value span');
var originalValue = $input.data('original-value');
if ($input.attr('id') === 'token-input-')
{
originalValue = token_value;
}
$value.html(_s(originalValue));
});
$editButton.fadeOut('fast', function() {
$editButton.remove();
});
removeEditForm();
};
var applyEdits = function($inputs, $editButton) {
if ($inputs.length) {
$inputs.animate({
opacity: 0.5
}, 500);
var data = {};
$inputs.each(function() {
var $input = $(this);
if ($input.is('[type=checkbox]')) {
data[$input.attr('name')] = $input.is(':checked') ? 'on' : 'off';
} else {
data[$input.attr('name')] = $input.val();
}
});
$editButton.fadeOut('fast', function() {
$editButton.remove();
});
var $loading = $('<div>').addClass('loading-overlay');
action.action({
data: data,
_custom: $detailView.data('_custom'),
$detailView: $detailView,
context: $detailView.data('view-args').context,
response: {
success: function(args) {
var notificationArgs = {
section: id,
desc: _l('changed.item.properties'),
_custom: args ? args._custom : null
};
if (!action.notification) {
convertInputs($inputs);
cloudStack.ui.notifications.add(
notificationArgs, function() {}, []
);
replaceListViewItem($detailView, data);
removeEditForm();
} else {
$loading.appendTo($detailView);
cloudStack.ui.notifications.add(
$.extend(true, {}, notificationArgs, action.notification),
function(args) {
replaceListViewItem($detailView, data);
convertInputs($inputs);
removeEditForm();
$loading.remove();
}, [],
function() {
$loading.remove();
$inputs.closest('.detail-view').find('.toolbar .refresh').click();
}, []
);
}
},
error: function(message) {
cancelEdits($inputs, $editButton);
if (message) cloudStack.dialog.notice({
message: message
});
}
}
});
}
};
$editButton.click(function() {
var $inputs = $detailView.find('input, select, textarea').filter(function() {
return !$(this).closest('.tagger').length;
});
var $form = $detailView.find('form').filter(function() {
return !$(this).closest('.tagger').length;
});
if ($(this).hasClass('done')) {
if (!$form.valid()) {
// Ignore hidden field validation
if ($form.find('input.error:visible, select.error:visible').length) {
return false;
}
}
restoreCopyPasteIcons();
applyEdits($inputs, $editButton);
} else { // Cancel
restoreCopyPasteIcons();
cancelEdits($inputs, $editButton);
}
return true;
});
$('div.container div.panel div.detail-group .details .main-groups').find('.reservediprange').toolTip({
docID: 'helpReservedIPRange',
mode: 'hover',
tooltip: '.tooltip-box'
});
$('div.container div.panel div.detail-group .details .main-groups').find('.networkcidr').toolTip({
docID: 'helpIPReservationNetworkCidr',
mode: 'hover',
tooltip: '.tooltip-box'
});
var restoreCopyPasteIcons = function() {
$detailView.find('td.value .copypasteicon').show();
};
$detailView.find('td.value span').each(function() {
var name = $(this).closest('tr').data('detail-view-field');
var $value = $(this);
if (!$value.data('detail-view-is-editable')) return true;
// Turn into form field
var selectData = $value.data('detail-view-editable-select');
var isBoolean = $value.data('detail-view-editable-boolean');
var textArea = $value.data('detail-view-editable-textarea');
var isTokenInput = $value.data('detail-view-is-token-input');
var data = !isBoolean ? cloudStack.sanitizeReverse($value.html()) : $value.data('detail-view-boolean-value');
var rules = $value.data('validation-rules') ? $value.data('validation-rules') : {};
var isPassword = $value.data('detail-view-is-password');
$value.html('');
if (selectData) {
// Select
$value.append(
$('<select>')
.attr({
name: name,
type: 'text',
value: data
})
.addClass('disallowSpecialCharacters').data('original-value', data)
);
// Make option values from given array
$(selectData).each(function() {
$('<option>')
.attr({
value: _s(this.id)
})
.html(_s(this.description))
.appendTo($value.find('select'));
});
$value.find('select').val($value.data('detail-view-selected-option'));
} else if (isBoolean) {
$value.append(
$('<input>').attr({
name: name,
type: 'checkbox',
checked: data
})
);
} else if (isTokenInput) { // jquery.tokeninput.js
function to_json_array(str) {
var simple_array = str.split(",");
var json_array = [];
$.each(simple_array, function(index, value) {
if ($.trim(value).length > 0)
{
var obj = {
id : value,
name : value
};
json_array.push(obj);
}
});
return json_array;
}
var existing_tags = to_json_array(data);
isAsync = true;
selectArgs = {
context: $detailView.data('view-args').context,
response: {
success: function(args) {
$input.tokenInput(unique_tags(args.data),
{
theme: "facebook",
preventDuplicates: true,
prePopulate: existing_tags,
processPrePopulate: true,
hintText: args.hintText,
noResultsText: args.noResultsText
});
}
}
};
$input = $('<input>').attr({
name: name,
type: 'text',
value: data
}).data('original-value', data);
$value.append($input);
token_value = data;
$value.data('value-token').dataProvider(selectArgs);
} else if (textArea) {
// Text area
$value.append(
$('<textarea>').attr({
name: name,
value: data
}).css({'min-height': '80px'}).data('original-value', data)
);
$value.css({'width': '100%', 'height': '100%'});
} else {
// Text input
$value.append(
$('<input>').attr({
name: name,
type: isPassword ? 'password' : 'text',
value: data
}).addClass('disallowSpecialCharacters').data('original-value', data)
);
}
if (rules && rules.required) {
var $required = $('<span>').addClass('field-required').text(' *');
$value.parent('td.value').prev('td.name').append($required);
}
return true;
});
if ($detailView.find('td.value span:data(detail-view-is-editable)').length) {
var $detailsEdit = $detailView.find('div.main-groups').detach(),
$detailsEditForm = $('<form>').append($detailsEdit);
$detailView.find('div.details').append($detailsEditForm);
}
// Setup form validation
var $form = $detailView.find('form').filter(function() {
return !$(this).closest('.tagger').length;
});
$form.validate();
$form.find('input, select').each(function() {
var data = $(this).parent('span').data('validation-rules');
if (data) {
$(this).rules('add', data);
} else {
$(this).rules('add', {});
}
});
return $detailView;
}
};
var viewAll = function(viewAllID, options) {
var $detailView = $('div.detail-view:visible:last');
var args = $detailView.data('view-args');
var cloudStackArgs = $('[cloudstack-container]').data('cloudStack-args');
var $browser = args.$browser;
var listViewArgs, viewAllPath;
var $listView;
var isCustom = $.isFunction(viewAllID.custom);
var updateContext = options.updateContext;
var customTitle = options.title;
if (isCustom) {
$browser.cloudBrowser('addPanel', {
title: _l(viewAllID.label),
maximizeIfSelected: true,
complete: function($newPanel) {
$newPanel.append(
viewAllID.custom({
$browser: $browser,
context: $detailView.data('view-args').context,
listViewArgs: $detailView.data('list-view') ? $detailView.data('list-view').data('view-args') : null
})
);
}
});
return;
}
// Get path in cloudStack args
viewAllPath = viewAllID.split('.');
if (viewAllPath.length == 2) {
if (viewAllPath[0] != '_zone') {
listViewArgs = $.extend(true, {}, cloudStackArgs.sections[viewAllPath[0]].sections[viewAllPath[1]]);
} else {
// Sub-section of the zone chart
listViewArgs = $.extend(true, {}, cloudStackArgs.sections.system
.subsections[viewAllPath[1]]);
}
} else {
listViewArgs = $.extend(true, {}, cloudStackArgs.sections[viewAllPath[0]]);
}
// Make list view
listViewArgs.$browser = $browser;
if (viewAllPath.length == 2)
listViewArgs.id = viewAllPath[0];
else
listViewArgs.id = viewAllID;
listViewArgs.ref = {
id: args.id,
type: $detailView.data('view-args').section
};
// Load context data
var context = $.extend(true, {}, $detailView.data('view-args').context);
if (updateContext) {
$.extend(context, updateContext({
context: context
}));
}
// Make panel
var $panel = $browser.cloudBrowser('addPanel', {
title: customTitle ? customTitle({
context: context
}) : _l(listViewArgs.title),
data: '',
noSelectPanel: true,
maximizeIfSelected: true,
complete: function($newPanel) {
return $('<div>').listView(listViewArgs, {
context: context
}).appendTo($newPanel);
}
});
};
/**
* Make action button elements
*
* @param actions {object} Actions to generate
*/
var makeActionButtons = function(actions, options) {
options = options ? options : {};
var $actions = $('<td>').addClass('detail-actions').append(
$('<div>').addClass('buttons')
);
var allowedActions = [];
if (actions) {
allowedActions = $.map(actions, function(value, key) {
return key;
});
if (options.actionFilter)
allowedActions = options.actionFilter({
context: $.extend(true, {}, options.context, {
actions: allowedActions,
item: options.data
})
});
$.each(actions, function(key, value) {
if ((!value.preFilter && $.inArray(key, allowedActions) == -1) ||
(value.preFilter && !value.preFilter({ context: options.context })) ||
(options.ignoreAddAction && key == 'add') ||
(key == 'edit' && options.compact)) {
return true;
}
var $action = $('<div></div>')
.addClass('action').addClass(key)
.appendTo($actions.find('div.buttons'))
.attr({
title: _l(value.label),
alt: _l(value.label)
});
var $actionLink = $('<a></a>')
.attr({
href: '#',
title: _l(value.label),
alt: _l(value.label),
'detail-action': key
})
.data('detail-view-action-callback', value.action)
.append(
$('<span>').addClass('icon').html('&nbsp;')
)
.appendTo($action);
if (value.textLabel || options.compact) {
$action
.addClass('single text')
.prepend(
$('<span>').addClass('label').html(
_l(
options.compact ?
(value.compactLabel ?
value.compactLabel : value.label) : value.textLabel
)
)
);
}
return true;
});
var $actionButtons = $actions.find('div.action:not(.text)');
if ($actionButtons.length == 1)
$actionButtons.addClass('single');
else {
$actionButtons.filter(':first').addClass('first');
$actionButtons.filter(':last').addClass('last');
}
}
return $('<div>')
.addClass('detail-group actions')
.append(
$('<table>').append(
$('<tbody>').append(
$('<tr>').append($actions)
)
)
);
};
/**
* Generate attribute field rows in tab
*/
var makeFieldContent = function(tabData, $detailView, data, args) {
if (!args) args = {};
var $detailGroups = $('<div>').addClass('details');
var isOddRow = false; // Even/odd row coloring
var $header;
var detailViewArgs = $detailView.data('view-args');
var fields = $.isArray(tabData.fields) ? tabData.fields.slice() : tabData.fields;
var hiddenFields;
var context = $.extend(true, {}, detailViewArgs ? detailViewArgs.context : cloudStack.context);
var isMultiple = tabData.multiple || tabData.isMultiple;
var actions = tabData.actions;
if (isMultiple) {
context[tabData.id] = [data];
$detailGroups.data('item-context', context);
}
// Make header
if (args.header) {
$detailGroups.addClass('group-multiple');
$header = $('<table>').addClass('header').appendTo($detailGroups);
$header.append($('<thead>').append($('<tr>')));
$header.find('tr').append($('<th>'));
}
if (tabData.preFilter) {
hiddenFields = tabData.preFilter({
context: context,
fields: $.map(fields, function(fieldGroup) {
return $.map(fieldGroup, function(value, key) {
return key;
});
})
});
}
$detailGroups.append($('<div>').addClass('main-groups'));
$(window).trigger('cloudStack.detailView.makeFieldContent', {
fields: fields,
data: data,
detailViewArgs: detailViewArgs,
$detailView: $detailView,
$detailGroups: $detailGroups
});
$(fields).each(function() {
var fieldGroup = this;
var $detailTable = $('<tbody></tbody>').appendTo(
$('<table></table>').appendTo(
$('<div></div>').addClass('detail-group').appendTo($detailGroups.find('.main-groups'))
));
$.each(fieldGroup, function(key, value) {
if (hiddenFields && $.inArray(key, hiddenFields) >= 0) return true;
if ($header && key == args.header) {
$header.find('th').html(_s(data[key]));
return true;
}
var $detail = $('<tr></tr>').addClass(key + '-row').appendTo($detailTable);
var $name = $('<td></td>').addClass('name').appendTo($detail);
var $value = $('<span>').appendTo($('<td></td>').addClass('value').appendTo($detail));
var content = data[key];
if (this.converter) content = this.converter(content);
$detail.data('detail-view-field', key);
// Even/odd row coloring
if (isOddRow && key != 'name') {
$detail.addClass('odd');
isOddRow = false;
} else if (key != 'name') {
isOddRow = true;
}
$name.html(_l(value.label));
$value.html(_s(content));
$value.attr('title', _s(content));
// Set up validation metadata
$value.data('validation-rules', value.validation);
//add copypaste icon
if (value.isCopyPaste) {
var $copyicon = $('<div>').addClass('copypasteicon').insertAfter($value);
$value.addClass('copypasteenabledvalue');
//set up copypaste eventhandler
$copyicon.click(function() {
//reset other values' formatting
$(this).closest('table').find('span.copypasteactive').removeClass('copypasteactive').addClass('copypasteenabledvalue');
//find the corresponding value
var $correspValue = $(this).closest('tr').find('.value').find('span');
$value.removeClass("copypasteenabledvalue").addClass("copypasteactive");
var correspValueElem = $correspValue.get(0);
//select the full value
var range = document.createRange();
range.selectNodeContents(correspValueElem);
var selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
});
}
// Set up editable metadata
if (typeof(value.isEditable) == 'function')
{
$value.data('detail-view-is-editable', value.isEditable(context));
}
else // typeof(value.isEditable) == 'boolean' or 'undefined'
{
$value.data('detail-view-is-editable', value.isEditable);
if (value.isTokenInput)
{
$value.data('detail-view-is-token-input', true);
$value.data('value-token', value);
}
}
if (value.select) {
value.selected = $value.html();
value.select({
context: context,
response: {
success: function(args) {
// Get matching select data
var matchedSelectValue = $.grep(args.data, function(option, index) {
return option.id == value.selected;
})[0];
if (matchedSelectValue != null) {
$value.html(_s(matchedSelectValue.description));
$value.data('detail-view-selected-option', matchedSelectValue.id);
}
$value.data('detail-view-editable-select', args.data);
return true;
}
}
});
} else if (value.isBoolean) {
$value.data('detail-view-editable-boolean', true);
$value.data('detail-view-boolean-value', content == 'Yes' ? true : false);
} else if (value.textArea) {
$value.data('detail-view-editable-textarea', true);
} else {
$value.data('detail-view-is-password', value.isPassword);
}
return true;
});
});
if (args.isFirstPanel) {
var $firstRow = $detailGroups.filter(':first').find('div.detail-group:first table tr:first');
var $actions;
var actions = detailViewArgs.actions;
var actionFilter = args.actionFilter;
// Detail view actions
if (actions || detailViewArgs.viewAll)
$actions = makeActionButtons(detailViewArgs.actions, {
actionFilter: actionFilter,
data: data,
context: $detailView.data('view-args').context,
compact: detailViewArgs.compact
}).prependTo($firstRow.closest('div.detail-group').closest('.details'));
// 'View all' button
var showViewAll = detailViewArgs.viewAll ?
(
detailViewArgs.viewAll.preFilter ?
detailViewArgs.viewAll.preFilter({
context: context
}) : true
) : true;
if ($actions && ($actions.find('div.action').length || (detailViewArgs.viewAll && showViewAll))) {
$actions.prependTo($firstRow.closest('div.detail-group').closest('.details'));
}
if (detailViewArgs.viewAll && showViewAll) {
if (!$.isArray(detailViewArgs.viewAll)) {
$('<div>')
.addClass('view-all')
.append(
$('<a>')
.attr({
href: '#'
})
.data('detail-view-link-view-all', detailViewArgs.viewAll)
.append(
$('<span>').html(_l('label.view') + ' ' + _l(detailViewArgs.viewAll.label))
)
)
.append(
$('<div>').addClass('end')
)
.appendTo(
$('<td>')
.addClass('view-all')
.appendTo($actions.find('tr'))
);
} else {
$(detailViewArgs.viewAll).each(function() {
var viewAllItem = this;
if (viewAllItem.preFilter && !viewAllItem.preFilter({
context: context
})) {
return true;
}
$('<div>')
.addClass('view-all')
.append(
$('<a>')
.attr({
href: '#'
})
.data('detail-view-link-view-all', viewAllItem)
.append(
$('<span>').html(_l('label.view') + ' ' + _l(viewAllItem.label))
)
)
.append(
$('<div>').addClass('end')
)
.appendTo(
$('<td>')
.addClass('view-all multiple')
.appendTo($actions.find('tr'))
);
$actions.find('td.view-all:first').addClass('first');
$actions.find('td.view-all:last').addClass('last');
$actions.find('td.detail-actions').addClass('full-length');
});
}
}
}
return $detailGroups;
};
/**
* Load field data for specific tab from data provider
*
* @param $tabContent {jQuery} tab div to load content into
* @param args {object} Detail view data
* @param options {object} Additional options
*/
var loadTabContent = function($tabContent, args, options) {
if (!options) options = {};
$tabContent.html('');
var targetTabID = $tabContent.data('detail-view-tab-id');
var tabList = args.tabs;
var tabs = tabList[targetTabID];
var dataProvider = tabs.dataProvider;
var isMultiple = tabs.multiple || tabs.isMultiple;
var viewAllArgs = args.viewAll;
var $detailView = $tabContent.closest('.detail-view');
var jsonObj = $detailView.data('view-args').jsonObj;
if (tabs.custom) {
return tabs.custom({
context: args.context
}).appendTo($tabContent);
}
$detailView.find('.detail-group:hidden').html('');
if (tabs.listView) {
return $('<div>').listView({
context: args.context,
listView: tabs.listView
}).appendTo($tabContent);
}
$.extend(
$detailView.data('view-args'), {
activeTab: targetTabID
}
);
if (!$detailView.data('view-args').compact) {
$tabContent.append(
$('<div>').addClass('loading-overlay')
);
}
return dataProvider({
tab: targetTabID,
id: args.id,
jsonObj: jsonObj,
context: $.extend(args.context, options),
response: {
success: function(args) {
if (options.newData) {
$.extend(args.data, options.newData);
}
if (args._custom) {
$detailView.data('_custom', args._custom);
}
var tabData = $tabContent.data('detail-view-tab-data');
var data = args.data;
var isFirstPanel = $tabContent.index($detailView.find('div.detail-group.ui-tabs-panel')) == 0;
var actionFilter = args.actionFilter;
$tabContent.find('.loading-overlay').remove();
if (isMultiple) {
$(data).each(function() {
var item = this;
var $fieldContent = makeFieldContent(
$.extend(true, {}, tabs, {
id: targetTabID
}),
$tabContent.closest('div.detail-view'), this, {
header: 'name',
isFirstPanel: isFirstPanel,
actionFilter: actionFilter
}
).appendTo($tabContent);
if (tabData.viewAll) {
$fieldContent.find('tr')
.filter('.' + tabData.viewAll.attachTo + '-row').find('td.value')
.append(
$('<div>').addClass('view-all').append(
$('<span>').html(
tabData.viewAll.label ?
_l(tabData.viewAll.label) :
_l('label.view.all')
),
$('<div>').addClass('end')
).click(function() {
viewAll(
tabData.viewAll.path, {
updateContext: function(args) {
var obj = {};
obj[targetTabID] = [item];
return obj;
},
title: tabData.viewAll.title
}
);
})
);
}
// Add action bar
if (tabData.multiple && tabData.actions) {
var $actions = makeActionButtons(tabData.actions, {
actionFilter: actionFilter,
data: item,
context: $.extend(true, {}, $detailView.data('view-args').context, {
item: [item]
}),
ignoreAddAction: true
});
$fieldContent.find('th').append($actions);
}
});
// Add item action
if (tabData.multiple && tabData.actions && tabData.actions.add) {
$tabContent.prepend(
$('<div>').addClass('button add').append(
$('<span>').addClass('icon').html('&nbsp;'),
$('<span>').html(_l(tabData.actions.add.label))
).click(function() {
uiActions.standard(
$detailView, {
tabs: tabList,
activeTab: targetTabID,
actions: tabData.actions,
actionName: 'add'
}, {
noRefresh: true,
complete: function(args) {
if ($detailView.is(':visible')) {
loadTabContent(
$detailView.find('div.detail-group:visible'),
$detailView.data('view-args')
);
}
}
}
)
})
);
}
return true;
}
makeFieldContent(tabs, $tabContent.closest('div.detail-view'), data, {
isFirstPanel: isFirstPanel,
actionFilter: actionFilter
}).appendTo($tabContent);
if (tabs.tags &&
$detailView.data('view-args') && !$detailView.data('view-args').compact) {
$('<div>').tagger(
$.extend(true, {}, tabs.tags, {
context: $detailView.data('view-args').context,
jsonObj: $detailView.data('view-args').jsonObj
})
).appendTo($detailView.find('.main-groups'));
}
if ($detailView.data('view-args').onLoad) {
$detailView.data('view-args').onLoad($detailView);
}
return true;
},
error: function() {
alert('error!');
}
}
});
};
var makeTabs = function($detailView, tabs, options) {
if (!options) options = {};
var $tabs = $('<ul>');
var $tabContentGroup = $('<div>');
var removedTabs = [];
var tabFilter = options.tabFilter;
var context = options.context ? options.context : {};
var updateContext = $detailView.data('view-args').updateContext;
var compact = options.compact;
if (updateContext) {
$.extend($detailView.data('view-args').context, updateContext({
context: $detailView.data('view-args').context
}));
}
if (options.newData &&
($detailView.data('view-args').section != null && context[$detailView.data('view-args').section] != null && context[$detailView.data('view-args').section].length > 0)) {
$.extend(
context[$detailView.data('view-args').section][0],
options.newData
);
}
if (tabFilter && !compact) {
removedTabs = tabFilter({
context: context
});
} else if (compact) {
removedTabs = $.grep(
$.map(
tabs,
function(value, key) {
return key;
}
), function(tab, index) {
return index > 0;
}
);
}
$.each(tabs, function(key, value) {
// Don't render tab, if filtered out
if ($.inArray(key, removedTabs) > -1) return true;
var propGroup = key;
var prop = value;
var title = prop.title;
var $tab = $('<li>').attr('detail-view-tab', true).appendTo($tabs);
var $tabLink = $('<a></a>').attr({
href: '#details-tab-' + propGroup
}).html(_l(title)).appendTo($tab);
var $tabContent = $('<div>').attr({
id: 'details-tab-' + propGroup
}).addClass('detail-group').appendTo($tabContentGroup);
$tabContent.data('detail-view-tab-id', key);
$tabContent.data('detail-view-tab-data', value);
return true;
});
$tabs.find('li:first').addClass('first');
$tabs.find('li:last').addClass('last');
return $.merge(
$tabs, $tabContentGroup.children()
);
};
var replaceTabs = function($detailView, tabs, options) {
var $detailViewElems = $detailView.find('ul.ui-tabs-nav, .detail-group');
$detailView.tabs('destroy');
$detailViewElems.remove();
makeTabs($detailView, tabs, options).appendTo($detailView);
};
var makeToolbar = function() {
return $('<div class="toolbar">')
.append(
$('<div>')
.addClass('button refresh')
.append(
$('<span>').html(_l('label.refresh'))
)
);
};
$.fn.detailView = function(args, options) {
var $detailView = this;
var compact = args.compact;
var $toolbar = makeToolbar();
var $tabs;
if (options == 'refresh') {
$tabs = replaceTabs($detailView, args.tabs, {
context: args.context,
tabFilter: args.tabFilter
});
} else {
$detailView.addClass('detail-view');
$detailView.data('view-args', args);
if (args.$listViewRow) {
$detailView.data('list-view-row', args.$listViewRow);
}
$tabs = makeTabs($detailView, args.tabs, {
compact: compact,
context: args.context,
tabFilter: args.tabFilter
});
$tabs.appendTo($detailView);
// Create toolbar
if (!compact) {
$toolbar.appendTo($detailView);
}
}
$detailView.tabs(
$.extend(true, {}, tabEvents, {
select: function() {
// Cleanup old tab content
$detailView.find('.detail-group').children().remove();
}}
)
);
return $detailView;
};
var manageTabsContent = function(event, ui){
var $target = $(event.target);
if (!$target.hasClass('detail-view') || $target.hasClass('detail-view ui-state-active')) return true;
var $targetDetailGroup = undefined;
if(ui.panel){
$targetDetailGroup = $(ui.panel);
}else{
$targetDetailGroup = $(ui.newPanel);
}
if(!$targetDetailGroup){
throw 'Could not find a panel to load tab\'s data';
}
loadTabContent($targetDetailGroup, $target.data('view-args'));
return true;
};
// View all links
$(document).on('click', 'a', function(event) {
var $target = $(event.target);
var $viewAll = $target.closest('td.view-all a');
var viewAllArgs;
if ($target.closest('div.detail-view').length && $target.closest('td.view-all a').length) {
viewAllArgs = $viewAll.data('detail-view-link-view-all');
viewAll(
viewAllArgs.custom ?
viewAllArgs :
viewAllArgs.path, {
updateContext: viewAllArgs.updateContext
}
);
return false;
}
return true;
});
// Setup view events
$(window).bind('cloudstack.view.details.remove', function(event, data) {
var $detailView = data.view;
$('#browser .container').cloudBrowser('selectPanel', {
panel: $detailView.closest('div.panel').prev()
});
});
// Setup action button events
$(document).bind('click', function(event) {
var $target = $(event.target);
// Refresh
if ($target.closest('div.toolbar div.refresh').length) {
loadTabContent(
$target.closest('div.detail-view').find('div.detail-group:visible'),
$target.closest('div.detail-view').data('view-args'),
{ refresh: true }
);
return false;
}
// Detail action
if ($target.closest('div.detail-view [detail-action], div.detail-view .action.text').length &&
!$target.closest('.list-view').length) {
var $action = $target.closest('.action').find('[detail-action]');
var actionName = $action.attr('detail-action');
var actionCallback = $action.data('detail-view-action-callback');
var detailViewArgs = $.extend(true, {}, $action.closest('div.detail-view').data('view-args'));
var additionalArgs = {};
var actionSet = uiActions;
var $details = $action.closest('.details');
var uiCallback = actionSet[actionName];
if (!uiCallback)
uiCallback = actionSet['standard'];
detailViewArgs.actionName = actionName;
if ($details.data('item-context')) {
detailViewArgs.context = $details.data('item-context');
}
uiCallback($target.closest('div.detail-view'), detailViewArgs, additionalArgs);
return false;
}
return true;
});
// Detail view refresh handler
$(window).bind('cloudStack.detailsRefresh', function() {
var $detailView = $('.detail-view');
$detailView.each(function() {
var $detailView = $(this),
args = $detailView.data('view-args');
$detailView.detailView(args, 'refresh');
});
});
}(window.jQuery, window.cloudStack, window._l));