mirror of https://github.com/apache/cloudstack.git
Implement tag UI widget
Create UI for handling new tag API. This currently supports the detail view and multi-edit
To enable tags UI, add a 'tags' object to each detailView/multiEdit configuration:
tabs: {
...
details: {
...
tags: {
actions: {
add: function(args) {
setTimeout(function() {
args.response.success({
notification: {
desc: 'Add tags for instance',
poll: testData.notifications.testPoll
}
});
}, 500);
},
remove: function(args) {
args.response.success({
notification: {
desc: 'Remove tags for instance',
poll: testData.notifications.testPoll
}
});
}
},
dataProvider: function(args) {
args.response.success({
data: [
{
id: '1',
key: 'user',
value: 'brian'
},
{
id: '2',
key: 'region',
value: 'usa'
}
]
});
}
}
...
This commit is contained in:
parent
f238ff9ff2
commit
ebabb15105
|
|
@ -8954,31 +8954,121 @@ div.panel.ui-dialog div.list-view div.fixed-header {
|
|||
|
||||
/*Tagger*/
|
||||
.tagger {
|
||||
overflow: hidden;
|
||||
width: 95%;
|
||||
background: #000000;
|
||||
width: 94%;
|
||||
margin: auto;
|
||||
padding-bottom: 12px;
|
||||
background: #F2F0F0;
|
||||
border: 1px solid #CFC9C9;
|
||||
/*+placement:shift -4px 0px;*/
|
||||
position: relative;
|
||||
left: -4px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.tagger input {
|
||||
overflow: hidden;
|
||||
.tagger .field {
|
||||
width: 179px;
|
||||
float: left;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tagger .tag-info {
|
||||
font-size: 11px;
|
||||
color: #757575;
|
||||
margin-top: 12px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.tagger .tag-info.title {
|
||||
font-size: 11px;
|
||||
color: #6F9BF0;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.tagger form {
|
||||
margin: 12px 9px 0px;
|
||||
}
|
||||
|
||||
.tagger.readonly form {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tagger form label {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 28px;
|
||||
text-align: right;
|
||||
font-size: 10px;
|
||||
color: #394552;
|
||||
margin-right: 9px;
|
||||
/*+placement:shift 5px 8px;*/
|
||||
position: relative;
|
||||
left: 5px;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.tagger form label.error {
|
||||
position: absolute;
|
||||
border: 1px solid #B2B2B2;
|
||||
color: #FF0000;
|
||||
left: 42px;
|
||||
top: 29px;
|
||||
/*[empty]background-color:;*/
|
||||
}
|
||||
|
||||
.tagger form input {
|
||||
padding: 4px;
|
||||
background: #FFFFFF;
|
||||
height: 15px;
|
||||
padding: 2px;
|
||||
border-left: none;
|
||||
border: 1px solid #808080;
|
||||
/*+border-radius:4px;*/
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
-khtml-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tagger form input[type=submit] {
|
||||
background: url(../images/bg-gradients.png) repeat-x 0px -220px;
|
||||
cursor: pointer;
|
||||
color: #FFFFFF;
|
||||
/*+text-shadow:0px -1px 2px #000000;*/
|
||||
-moz-text-shadow: 0px -1px 2px #000000;
|
||||
-webkit-text-shadow: 0px -1px 2px #000000;
|
||||
-o-text-shadow: 0px -1px 2px #000000;
|
||||
text-shadow: 0px -1px 2px #000000;
|
||||
border: none;
|
||||
/*+border-radius:4px;*/
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
-khtml-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
padding: 7px 25px 7px 26px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.tagger form input[type=submit]:hover {
|
||||
background-position: 0px -946px;
|
||||
}
|
||||
|
||||
.tagger ul {
|
||||
height: 19px;
|
||||
border: 1px solid #B2B2B2;
|
||||
border-right: none;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 13px;
|
||||
width: 96%;
|
||||
margin: 16px auto auto;
|
||||
/*+border-radius:2px;*/
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
-khtml-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
overflow: auto;
|
||||
padding-bottom: 10px;
|
||||
border: 1px solid #D2D2D2;
|
||||
background: #FFFFFF;
|
||||
overflow: hidden;
|
||||
/*+box-shadow:inset 0px 0px 10px #DCDCDC;*/
|
||||
-moz-box-shadow: inset 0px 0px 10px #DCDCDC;
|
||||
-webkit-box-shadow: inset 0px 0px 10px #DCDCDC;
|
||||
-o-box-shadow: inset 0px 0px 10px #DCDCDC;
|
||||
box-shadow: inset 0px 0px 10px #DCDCDC;
|
||||
}
|
||||
|
||||
.tagger.readonly ul {
|
||||
}
|
||||
|
||||
.tagger ul li {
|
||||
|
|
@ -8987,7 +9077,9 @@ div.panel.ui-dialog div.list-view div.fixed-header {
|
|||
padding: 0px 18px 0 7px;
|
||||
display: inline-block;
|
||||
float: left;
|
||||
margin-left: 7px;
|
||||
margin-right: 2px;
|
||||
margin-top: 5px;
|
||||
/*+border-radius:4px;*/
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
|
|
@ -9000,25 +9092,25 @@ div.panel.ui-dialog div.list-view div.fixed-header {
|
|||
}
|
||||
|
||||
.tagger ul li span {
|
||||
width: auto !important;
|
||||
top: 2px !important;
|
||||
left: 5px !important;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.tagger ul li span.label {
|
||||
display: none;
|
||||
/*+placement:shift 12px 2px;*/
|
||||
position: relative !important;
|
||||
left: 12px !important;
|
||||
top: 2px !important;
|
||||
font-size: 10px;
|
||||
position: relative;
|
||||
left: 15px;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.tagger.readonly ul li span.label {
|
||||
left: 6px;
|
||||
}
|
||||
|
||||
.tagger ul li span.remove {
|
||||
width: 15px !important;
|
||||
overflow: hidden !important;
|
||||
height: 11px !important;
|
||||
background: #DFDFDF url(../images/sprites.png) no-repeat -595px -1183px;
|
||||
background: #DFDFDF url(../images/sprites.png) no-repeat -596px -1183px;
|
||||
display: block;
|
||||
top: 0px !important;
|
||||
left: -3px !important;
|
||||
|
|
@ -9031,10 +9123,33 @@ div.panel.ui-dialog div.list-view div.fixed-header {
|
|||
color: #5B5B5B;
|
||||
}
|
||||
|
||||
.tagger.readonly ul li span.remove {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tagger ul li span.remove:hover {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
/** Dialog tagger*/
|
||||
.ui-dialog .tagger {
|
||||
}
|
||||
|
||||
.ui-dialog .tagger .field {
|
||||
width: 119px !important;
|
||||
}
|
||||
|
||||
.ui-dialog .tagger input.key,
|
||||
.ui-dialog .tagger input.value {
|
||||
width: 66px !important;
|
||||
height: 15px;
|
||||
font-size: 11px !important;
|
||||
}
|
||||
|
||||
.ui-dialog .tagger input[type=submit] {
|
||||
padding: 6px 15px;
|
||||
}
|
||||
|
||||
/*VPC / vApps*/
|
||||
.vpc-chart {
|
||||
width: 100%;
|
||||
|
|
|
|||
|
|
@ -281,10 +281,14 @@
|
|||
* @param callback
|
||||
*/
|
||||
edit: function($detailView, args) {
|
||||
$detailView.addClass('edit-mode');
|
||||
|
||||
if ($detailView.find('.button.done').size()) return false;
|
||||
|
||||
// Convert value TDs
|
||||
var $inputs = $detailView.find('input, select');
|
||||
var $inputs = $detailView.find('input, select').filter(function() {
|
||||
return !$(this).closest('.tagger').size() && !$(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();
|
||||
|
|
@ -296,9 +300,14 @@
|
|||
$detailView.find('.ui-tabs-panel .detail-group.actions')
|
||||
).fadeIn();
|
||||
|
||||
$detailView.find('.tagger').removeClass('readonly');
|
||||
$detailView.find('.tagger').find('input[type=text]').val('');
|
||||
|
||||
var convertInputs = function($inputs) {
|
||||
// Save and turn back into labels
|
||||
$inputs.each(function() {
|
||||
if ($(this).closest('.tagger').size()) return true;
|
||||
|
||||
var $input = $(this);
|
||||
var $value = $input.closest('td.value span');
|
||||
|
||||
|
|
@ -322,8 +331,12 @@
|
|||
};
|
||||
|
||||
var removeEditForm = function() {
|
||||
$detailView.removeClass('edit-mode');
|
||||
|
||||
// Remove Edit form
|
||||
var $form = $detailView.find('form');
|
||||
var $form = $detailView.find('form').filter(function() {
|
||||
return !$(this).closest('.tagger').size();
|
||||
});
|
||||
if ($form.size()) {
|
||||
var $mainGroups = $form.find('div.main-groups').detach();
|
||||
$form.parent('div').append($mainGroups);
|
||||
|
|
@ -331,11 +344,15 @@
|
|||
}
|
||||
//Remove required labels
|
||||
$detailView.find('span.field-required').remove();
|
||||
}
|
||||
$detailView.find('.tagger').addClass('readonly');
|
||||
|
||||
};
|
||||
|
||||
// Put in original values
|
||||
var cancelEdits = function($inputs, $editButton) {
|
||||
$inputs.each(function() {
|
||||
if ($(this).closest('.tagger').size()) return true;
|
||||
|
||||
var $input = $(this);
|
||||
var $value = $input.closest('td.value span');
|
||||
var originalValue = $input.data('original-value');
|
||||
|
|
@ -418,8 +435,12 @@
|
|||
};
|
||||
|
||||
$editButton.click(function() {
|
||||
var $inputs = $detailView.find('input, select'),
|
||||
$form = $detailView.find('form');
|
||||
var $inputs = $detailView.find('input, select').filter(function() {
|
||||
return !$(this).closest('.tagger').size();
|
||||
});
|
||||
var $form = $detailView.find('form').filter(function() {
|
||||
return !$(this).closest('.tagger').size();
|
||||
});
|
||||
|
||||
if ($(this).hasClass('done')) {
|
||||
if (!$form.valid()) {
|
||||
|
|
@ -432,6 +453,8 @@
|
|||
} else { // Cancel
|
||||
cancelEdits($inputs, $editButton);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
$detailView.find('td.value span').each(function() {
|
||||
|
|
@ -487,10 +510,6 @@
|
|||
value: data
|
||||
}).data('original-value', data)
|
||||
);
|
||||
|
||||
if ($value.closest('tr').data('detail-view-is-tagged')) {
|
||||
$value.find('input').tagger();
|
||||
}
|
||||
}
|
||||
|
||||
if (rules && rules.required) {
|
||||
|
|
@ -509,8 +528,11 @@
|
|||
}
|
||||
|
||||
// Setup form validation
|
||||
$detailView.find('form').validate();
|
||||
$detailView.find('form').find('input, select').each(function() {
|
||||
var $form = $detailView.find('form').filter(function() {
|
||||
return !$(this).closest('.tagger').size();
|
||||
});
|
||||
$form.validate();
|
||||
$form.find('input, select').each(function() {
|
||||
var data = $(this).parent('span').data('validation-rules');
|
||||
if (data) {
|
||||
$(this).rules('add', data);
|
||||
|
|
@ -762,10 +784,6 @@
|
|||
// Set up validation metadata
|
||||
$value.data('validation-rules', value.validation);
|
||||
|
||||
if (value.isTag) {
|
||||
$detail.data('detail-view-is-tagged', true);
|
||||
}
|
||||
|
||||
// Set up editable metadata
|
||||
if(typeof(value.isEditable) == 'function')
|
||||
$value.data('detail-view-is-editable', value.isEditable());
|
||||
|
|
@ -936,6 +954,14 @@
|
|||
actionFilter: actionFilter
|
||||
}).appendTo($tabContent);
|
||||
|
||||
if (tabs.tags) {
|
||||
$('<div>').tagger(
|
||||
$.extend(true, {}, tabs.tags, {
|
||||
context: $detailView.data('view-args').context
|
||||
})
|
||||
).appendTo($tabContent).addClass('readonly');
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
error: function() {
|
||||
|
|
|
|||
|
|
@ -328,10 +328,16 @@
|
|||
after: function(args) {
|
||||
var $loading = $('<div>').addClass('loading-overlay').prependTo($dataItem);
|
||||
performAction({ data: args.data, complete: function() {
|
||||
$multi.multiEdit('refresh');
|
||||
$multi.trigger('refresh');
|
||||
} });
|
||||
}
|
||||
});
|
||||
|
||||
if (options.tags) {
|
||||
$(':ui-dialog').append(
|
||||
$('<div>').addClass('multi-edit-tags').tagger(options.tags)
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
|
@ -647,6 +653,7 @@
|
|||
$.fn.multiEdit = function(args) {
|
||||
var dataProvider = args.dataProvider;
|
||||
var multipleAdd = args.multipleAdd;
|
||||
var tags = args.tags;
|
||||
var $multi = $('<div>').addClass('multi-edit').appendTo(this);
|
||||
var $multiForm = $('<form>').appendTo($multi);
|
||||
var $inputTable = $('<table>').addClass('multi-edit').appendTo($multiForm);
|
||||
|
|
@ -913,7 +920,8 @@
|
|||
context: $.extend(true, {}, context, this._context),
|
||||
ignoreEmptyFields: ignoreEmptyFields,
|
||||
preFilter: actionPreFilter,
|
||||
listView: listView
|
||||
listView: listView,
|
||||
tags: tags
|
||||
}
|
||||
).appendTo($dataBody);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,90 +1,197 @@
|
|||
(function($) {
|
||||
(function($, cloudStack) {
|
||||
var elems = {
|
||||
inputArea: function(args) {
|
||||
var $form = $('<form>').addClass('tag-input');
|
||||
var $keyField = $('<div>').addClass('field key');
|
||||
var $keyLabel = $('<label>').attr('for', 'key').html('Key:');
|
||||
var $key = $('<input>').addClass('key required').attr('name', 'key');
|
||||
var $valueField = $('<div>').addClass('field value');
|
||||
var $valueLabel = $('<label>').attr('for', 'value').html('Value:');
|
||||
var $value = $('<input>').addClass('value required').attr('name', 'value');
|
||||
var $submit = $('<input>').attr('type', 'submit').val('Add');
|
||||
|
||||
$keyField.append($keyLabel, $key);
|
||||
$valueField.append($valueLabel, $value);
|
||||
$form.append(
|
||||
$keyField, $valueField,
|
||||
$submit
|
||||
);
|
||||
|
||||
$form.validate({ onfocusout: false });
|
||||
|
||||
$form.submit(
|
||||
args.onSubmit ?
|
||||
function() {
|
||||
if (!$form.valid()) return false;
|
||||
|
||||
args.onSubmit({
|
||||
data: cloudStack.serializeForm($form),
|
||||
response: {
|
||||
success: function() {
|
||||
// Restore editing of input
|
||||
$key.attr('disabled', false);
|
||||
$value.attr('disabled', false);
|
||||
|
||||
// Clear out old data
|
||||
$key.val(''); $value.val('');
|
||||
$key.focus();
|
||||
},
|
||||
error: function() {
|
||||
// Restore editing of input
|
||||
$key.attr('disabled', false);
|
||||
$value.attr('disabled', false);
|
||||
$key.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent input during submission
|
||||
$key.attr('disabled', 'disabled');
|
||||
$value.attr('disabled', 'disabled');
|
||||
|
||||
return false;
|
||||
} :
|
||||
function() { return false; }
|
||||
);
|
||||
|
||||
return $form;
|
||||
},
|
||||
tagItem: function(title, onRemove) {
|
||||
var $li = $('<li>');
|
||||
var $label = $('<span>').addClass('label').html(title);
|
||||
var $remove = $('<span>').addClass('remove').html('X');
|
||||
|
||||
$remove.click(function() {
|
||||
$li.remove();
|
||||
|
||||
if (onRemove) onRemove();
|
||||
if (onRemove) onRemove($li);
|
||||
});
|
||||
|
||||
$li.append($remove, $label);
|
||||
|
||||
return $li;
|
||||
},
|
||||
|
||||
info: function(text) {
|
||||
var $info = $('<div>').addClass('tag-info');
|
||||
var $text = $('<span>').html(text);
|
||||
|
||||
$text.appendTo($info);
|
||||
|
||||
return $info;
|
||||
}
|
||||
};
|
||||
|
||||
$.widget('cloudStack.tagger', {
|
||||
_init: function() {
|
||||
var $container = $('<div>').addClass('tagger');
|
||||
_init: function(args) {
|
||||
var context = this.options.context;
|
||||
var dataProvider = this.options.dataProvider;
|
||||
var actions = this.options.actions;
|
||||
var $container = this.element.addClass('tagger');
|
||||
var $tagArea = $('<ul>').addClass('tags');
|
||||
var $originalInput = this.element;
|
||||
var $input = $('<input>').attr('type', 'text');
|
||||
var $title = elems.info('Tags').addClass('title');
|
||||
var $loading = $('<div>').addClass('loading-overlay');
|
||||
|
||||
$originalInput.hide();
|
||||
$originalInput.after($container);
|
||||
$container.append($tagArea, $input);
|
||||
var onRemoveItem = function($item) {
|
||||
$loading.appendTo($container);
|
||||
actions.remove({
|
||||
context: context,
|
||||
response: {
|
||||
success: function(args) {
|
||||
var notification = $.extend(true, {} , args.notification, {
|
||||
interval: 500,
|
||||
_custom: args._custom
|
||||
});
|
||||
|
||||
cloudStack.ui.notifications.add(
|
||||
notification,
|
||||
|
||||
// Reposition input to fit tag list
|
||||
var relayout = function() {
|
||||
$input.width(
|
||||
$container.width() - $tagArea.width() - 25
|
||||
);
|
||||
$input.css({
|
||||
left: $tagArea.width(),
|
||||
top: $tagArea.position().top
|
||||
// Success
|
||||
function() {
|
||||
$loading.remove();
|
||||
$item.remove();
|
||||
}, {},
|
||||
|
||||
// Error
|
||||
function() {
|
||||
$loading.remove();
|
||||
}, {}
|
||||
);
|
||||
},
|
||||
error: function(message) {
|
||||
$loading.remove();
|
||||
cloudStack.dialog.notice({ message: message });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var $inputArea = elems.inputArea({
|
||||
onSubmit: function(args) {
|
||||
var data = args.data;
|
||||
var success = args.response.success;
|
||||
var error = args.response.error;
|
||||
var title = data.key + ' = ' + data.value;
|
||||
|
||||
var onRemove = function() {
|
||||
syncInputs(true);
|
||||
relayout();
|
||||
};
|
||||
$loading.appendTo($container);
|
||||
actions.add({
|
||||
context: context,
|
||||
response: {
|
||||
success: function(args) {
|
||||
var notification = $.extend(true, {} , args.notification, {
|
||||
interval: 500,
|
||||
_custom: args._custom
|
||||
});
|
||||
|
||||
// sync original input box and tag list values
|
||||
//
|
||||
// flag == true: Sync tags->text
|
||||
// flag == false: Sync text->tags
|
||||
var syncInputs = function(flag) {
|
||||
if (flag) {
|
||||
$originalInput.val(
|
||||
$tagArea.find('li').map(function(index, tag) {
|
||||
return $(tag).find('span.label').html();
|
||||
}).toArray().join(',')
|
||||
);
|
||||
} else if ($originalInput.val()) {
|
||||
$($originalInput.val().split(',')).map(function(index, tag) {
|
||||
elems.tagItem(tag, onRemove).appendTo($tagArea);
|
||||
});
|
||||
cloudStack.ui.notifications.add(
|
||||
notification,
|
||||
|
||||
$tagArea.show();
|
||||
relayout();
|
||||
}
|
||||
};
|
||||
// Success
|
||||
function() {
|
||||
$loading.remove();
|
||||
elems.tagItem(title, onRemoveItem).appendTo($tagArea);
|
||||
success();
|
||||
}, {},
|
||||
|
||||
// Tag detection (comma-delimited)
|
||||
$input.keypress(function(event) {
|
||||
var tagCode = 44; // Symbol used to indicate a new tag
|
||||
|
||||
if (event.which == tagCode) {
|
||||
$tagArea.show();
|
||||
elems.tagItem($input.val(), onRemove).appendTo($tagArea);
|
||||
$input.val('');
|
||||
relayout();
|
||||
syncInputs(true);
|
||||
|
||||
return false; // Don't allow delineator to be added to input box
|
||||
// Error
|
||||
function() {
|
||||
$loading.remove();
|
||||
error();
|
||||
}, {}
|
||||
);
|
||||
},
|
||||
error: function(message) {
|
||||
$loading.remove();
|
||||
error();
|
||||
cloudStack.dialog.notice({ message: message });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
$tagArea.hide();
|
||||
relayout();
|
||||
syncInputs(false);
|
||||
$container.append($title, $inputArea, $tagArea);
|
||||
|
||||
// Get data
|
||||
$loading.appendTo($container);
|
||||
dataProvider({
|
||||
context: context,
|
||||
response: {
|
||||
success: function(args) {
|
||||
var data = args.data;
|
||||
|
||||
$loading.remove();
|
||||
$(data).map(function(index, item) {
|
||||
var key = item.key;
|
||||
var value = item.value;
|
||||
|
||||
elems.tagItem(key + ' = ' + value, onRemoveItem).appendTo($tagArea);
|
||||
});
|
||||
},
|
||||
error: function(message) {
|
||||
$loading.remove();
|
||||
$container.find('ul').html(message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}(jQuery));
|
||||
}(jQuery, cloudStack));
|
||||
|
|
|
|||
Loading…
Reference in New Issue