mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Add support for dropping multiple objects at once from the collection Properties panel. Fixes #1513
This commit is contained in:
committed by
Dave Page
parent
3359a0de7a
commit
f17979141c
@@ -1,7 +1,11 @@
|
||||
//import * as commonUtils from '../../../static/js/utils';
|
||||
//import Mousetrap from 'mousetrap';
|
||||
|
||||
define([
|
||||
'sources/gettext', 'jquery', 'underscore', 'underscore.string', 'sources/pgadmin',
|
||||
'backbone', 'alertify', 'backform', 'backgrid', 'sources/browser/generate_url', 'pgadmin.backform', 'pgadmin.backgrid',
|
||||
'pgadmin.browser.node',
|
||||
'backbone', 'alertify', 'backform', 'backgrid', 'sources/browser/generate_url',
|
||||
'pgadmin.backform', 'pgadmin.backgrid',
|
||||
'pgadmin.browser.node', 'backgrid.select.all',
|
||||
], function(gettext, $, _, S, pgAdmin, Backbone, Alertify, Backform, Backgrid, generateUrl) {
|
||||
|
||||
var pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
@@ -48,12 +52,15 @@ define([
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
hasId: false,
|
||||
is_collection: true,
|
||||
collection_node: true,
|
||||
// A collection will always have a collection of statistics, when the node
|
||||
// it represent will have some statistics.
|
||||
hasCollectiveStatistics: true,
|
||||
canDrop: true,
|
||||
canDropCascade: true,
|
||||
showProperties: function(item, data, panel) {
|
||||
var that = this,
|
||||
j = panel.$container.find('.obj_properties').first(),
|
||||
@@ -71,25 +78,112 @@ define([
|
||||
gridSchema = Backform.generateGridColumnsFromModel(
|
||||
info, node.model, 'properties', that.columns
|
||||
),
|
||||
// Initialize a new Grid instance
|
||||
grid = new Backgrid.Grid({
|
||||
columns: gridSchema.columns,
|
||||
collection: collection,
|
||||
className: 'backgrid table-bordered',
|
||||
}),
|
||||
gridView = {
|
||||
'remove': function() {
|
||||
if (this.grid) {
|
||||
if (this.grid.collection) {
|
||||
this.grid.collection.reset(null, {silent: true});
|
||||
delete (this.grid.collection);
|
||||
}
|
||||
delete (this.grid);
|
||||
this.grid = null;
|
||||
createButtons = function(buttons, location, extraClasses) {
|
||||
// Arguments must be non-zero length array of type
|
||||
// object, which contains following attributes:
|
||||
// label, type, extraClasses, register
|
||||
if (buttons && _.isArray(buttons) && buttons.length > 0) {
|
||||
// All buttons will be created within a single
|
||||
// div area.
|
||||
var btnGroup =
|
||||
$('<div></div>').addClass(
|
||||
'pg-prop-btn-group'
|
||||
),
|
||||
// Template used for creating a button
|
||||
tmpl = _.template([
|
||||
'<button type="<%= type %>" ',
|
||||
'class="btn <%=extraClasses.join(\' \')%>"',
|
||||
'<% if (disabled) { %> disabled="disabled"<% } %> title="<%-tooltip%>">',
|
||||
'<span class="<%= icon %>"></span><% if (label != "") { %> <%-label%><% } %></button>',
|
||||
].join(' '));
|
||||
if (location == 'header') {
|
||||
btnGroup.appendTo(that.header);
|
||||
} else {
|
||||
btnGroup.appendTo(that.footer);
|
||||
}
|
||||
},
|
||||
grid: grid,
|
||||
};
|
||||
if (extraClasses) {
|
||||
btnGroup.addClass(extraClasses);
|
||||
}
|
||||
_.each(buttons, function(btn) {
|
||||
// Create the actual button, and append to
|
||||
// the group div
|
||||
|
||||
// icon may not present for this button
|
||||
if (!btn.icon) {
|
||||
btn.icon = '';
|
||||
}
|
||||
var b = $(tmpl(btn));
|
||||
btnGroup.append(b);
|
||||
// Register is a callback to set callback
|
||||
// for certain operation for this button.
|
||||
btn.register(b);
|
||||
});
|
||||
return btnGroup;
|
||||
}
|
||||
return null;
|
||||
}.bind(panel);
|
||||
|
||||
// Add the new column for the multi-select menus
|
||||
if (that.canDrop || that.canDropCascade) {
|
||||
gridSchema.columns.unshift({
|
||||
name: 'oid',
|
||||
cell: Backgrid.Extension.SelectRowCell.extend({
|
||||
initialize: function (options) {
|
||||
this.column = options.column;
|
||||
if (!(this.column instanceof Backgrid.Column)) {
|
||||
this.column = new Backgrid.Column(this.column);
|
||||
}
|
||||
|
||||
var column = this.column, model = this.model, $el = this.$el;
|
||||
this.listenTo(column, 'change:renderable', function (column, renderable) {
|
||||
$el.toggleClass('renderable', renderable);
|
||||
});
|
||||
|
||||
if (Backgrid.callByNeed(column.renderable(), column, model)) $el.addClass('renderable');
|
||||
|
||||
this.listenTo(model, 'backgrid:select', this.toggleCheckbox);
|
||||
},
|
||||
toggleCheckbox: function(model, selected) {
|
||||
if (this.checkbox().prop('disabled') === false) {
|
||||
this.checkbox().prop('checked', selected).change();
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
let model = this.model.toJSON();
|
||||
|
||||
// canDrop can be set to false for individual row from the server side to disable the checkbox
|
||||
if ('canDrop' in model && model.canDrop === false)
|
||||
this.$el.empty().append('<input tabindex="-1" type="checkbox" disabled="disabled"/>');
|
||||
else
|
||||
this.$el.empty().append('<input tabindex="-1" type="checkbox" />');
|
||||
|
||||
this.delegateEvents();
|
||||
return this;
|
||||
},
|
||||
}),
|
||||
headerCell: Backgrid.Extension.SelectAllHeaderCell,
|
||||
});
|
||||
}
|
||||
// Initialize a new Grid instance
|
||||
that.grid = new Backgrid.Grid({
|
||||
columns: gridSchema.columns,
|
||||
collection: collection,
|
||||
className: 'backgrid table-bordered',
|
||||
});
|
||||
|
||||
var gridView = {
|
||||
'remove': function() {
|
||||
if (this.grid) {
|
||||
if (this.grid.collection) {
|
||||
this.grid.collection.reset(null, {silent: true});
|
||||
delete (this.grid.collection);
|
||||
}
|
||||
delete (this.grid);
|
||||
this.grid = null;
|
||||
}
|
||||
},
|
||||
grid: that.grid,
|
||||
};
|
||||
|
||||
if (view) {
|
||||
// Avoid unnecessary reloads
|
||||
@@ -108,8 +202,45 @@ define([
|
||||
j.empty();
|
||||
j.data('obj-view', gridView);
|
||||
|
||||
that.header = $('<div></div>').addClass(
|
||||
'pg-prop-header'
|
||||
).appendTo(j);
|
||||
|
||||
// Render the buttons
|
||||
var buttons = [];
|
||||
|
||||
buttons.push({
|
||||
label: '',
|
||||
type: 'delete',
|
||||
tooltip: gettext('Delete/Drop'),
|
||||
extraClasses: ['btn-default', 'delete_multiple'],
|
||||
icon: 'fa fa-lg fa-trash-o',
|
||||
disabled: !that.canDrop,
|
||||
register: function(btn) {
|
||||
btn.on('click',() => {
|
||||
onDrop('drop');
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
buttons.push({
|
||||
label: '',
|
||||
type: 'delete',
|
||||
tooltip: gettext('Drop Cascade'),
|
||||
extraClasses: ['btn-default', 'delete_multiple_cascade'],
|
||||
icon: '',
|
||||
disabled: !that.canDropCascade,
|
||||
register: function(btn) {
|
||||
btn.on('click',() => {
|
||||
onDrop('dropCascade');
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
createButtons(buttons, 'header', 'pg-prop-btn-group-above bg-gray-lighter border-gray-light');
|
||||
|
||||
// Render subNode grid
|
||||
content.append(grid.render().$el);
|
||||
content.append(that.grid.render().$el);
|
||||
j.append(content);
|
||||
|
||||
// Fetch Data
|
||||
@@ -131,6 +262,88 @@ define([
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
var onDrop = function(type) {
|
||||
let sel_row_models = this.grid.getSelectedModels(),
|
||||
sel_rows = [],
|
||||
item = pgBrowser.tree.selected(),
|
||||
d = item ? pgBrowser.tree.itemData(item) : null,
|
||||
node = pgBrowser.Nodes[d._type],
|
||||
url = undefined,
|
||||
msg = undefined,
|
||||
title = undefined;
|
||||
|
||||
_.each(sel_row_models, function(r){ sel_rows.push(r.id); });
|
||||
|
||||
if (sel_rows.length === 0) {
|
||||
Alertify.alert(gettext('Drop Multiple'),
|
||||
gettext('Please select at least one object to delete.')
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'dropCascade') {
|
||||
url = node.generate_url(item, 'delete'),
|
||||
msg = gettext('Are you sure you want to drop all the selected objects and all the objects that depend on them?'),
|
||||
title = gettext('DROP CASCADE multiple objects?');
|
||||
} else {
|
||||
url = node.generate_url(item, 'drop');
|
||||
msg = gettext('Are you sure you want to drop all the selected objects?');
|
||||
title = gettext('DROP multiple objects?');
|
||||
}
|
||||
|
||||
|
||||
Alertify.confirm(title, msg,
|
||||
function() {
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'DELETE',
|
||||
data: JSON.stringify({'ids': sel_rows}),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
})
|
||||
.done(function(res) {
|
||||
if (res.success == 0) {
|
||||
pgBrowser.report_error(res.errormsg, res.info);
|
||||
} else {
|
||||
$(pgBrowser.panels['properties'].panel).removeData('node-prop');
|
||||
pgBrowser.Events.trigger(
|
||||
'pgadmin:browser:tree:refresh', item || pgBrowser.tree.selected(), {
|
||||
success: function() {
|
||||
node.callbacks.selected.apply(node, [item]);
|
||||
},
|
||||
});
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.fail(function(jqx) {
|
||||
var msg = jqx.responseText;
|
||||
/* Error from the server */
|
||||
if (jqx.status == 417 || jqx.status == 410 || jqx.status == 500) {
|
||||
try {
|
||||
var data = JSON.parse(jqx.responseText);
|
||||
msg = data.errormsg;
|
||||
} catch (e) {
|
||||
console.warn(e.stack || e);
|
||||
}
|
||||
}
|
||||
pgBrowser.report_error(
|
||||
S(gettext('Error dropping %s'))
|
||||
.sprintf(d._label.toLowerCase())
|
||||
.value(), msg);
|
||||
|
||||
$(pgBrowser.panels['properties'].panel).removeData('node-prop');
|
||||
pgBrowser.Events.trigger(
|
||||
'pgadmin:browser:tree:refresh', item || pgBrowser.tree.selected(), {
|
||||
success: function() {
|
||||
node.callbacks.selected.apply(node, [item]);
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
null).show();
|
||||
return;
|
||||
}.bind(that);
|
||||
},
|
||||
generate_url: function(item, type) {
|
||||
/*
|
||||
@@ -138,7 +351,9 @@ define([
|
||||
* under the collection, and properties of the collection respectively.
|
||||
*/
|
||||
var opURL = {
|
||||
'properties': 'obj', 'children': 'nodes',
|
||||
'properties': 'obj',
|
||||
'children': 'nodes',
|
||||
'drop': 'obj',
|
||||
},
|
||||
self = this;
|
||||
var collectionPickFunction = function (treeInfoValue, treeInfoKey) {
|
||||
|
||||
@@ -30,6 +30,9 @@ _.extend(pgBrowser.keyboardNavigation, {
|
||||
'sub_menu_refresh': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_refresh').value),
|
||||
'context_menu': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'context_menu').value),
|
||||
'direct_debugging': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'direct_debugging').value),
|
||||
'drop_multiple_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'grid_menu_drop_multiple').value),
|
||||
'drop_cascade_multiple_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'grid_menu_drop_cascade_multiple').value),
|
||||
|
||||
};
|
||||
this.shortcutMethods = {
|
||||
'bindMainMenu': {
|
||||
@@ -48,6 +51,8 @@ _.extend(pgBrowser.keyboardNavigation, {
|
||||
'bindSubMenuRefresh': {'shortcuts': this.keyboardShortcut.sub_menu_refresh, 'bindElem': '#tree'}, // Sub menu - Refresh object,
|
||||
'bindContextMenu': {'shortcuts': this.keyboardShortcut.context_menu}, // Sub menu - Open context menu,
|
||||
'bindDirectDebugging': {'shortcuts': this.keyboardShortcut.direct_debugging}, // Sub menu - Direct Debugging
|
||||
'bindDropMultipleObjects': {'shortcuts': this.keyboardShortcut.drop_multiple_objects}, // Grid Menu Drop Multiple
|
||||
'bindDropCascadeMultipleObjects': {'shortcuts': this.keyboardShortcut.drop_cascade_multiple_objects}, // Grid Menu Drop Cascade Multiple
|
||||
};
|
||||
this.bindShortcuts();
|
||||
}
|
||||
@@ -308,6 +313,26 @@ _.extend(pgBrowser.keyboardNavigation, {
|
||||
pgAdmin.Tools.Debugger.get_function_information(pgAdmin.Browser.Nodes[type]);
|
||||
}
|
||||
},
|
||||
bindDropMultipleObjects: function() {
|
||||
let isPropertyPanelVisible = this.isPropertyPanelVisible();
|
||||
if (isPropertyPanelVisible === true && $('button.delete_multiple').length > 0) {
|
||||
$('button.delete_multiple').click();
|
||||
}
|
||||
},
|
||||
bindDropCascadeMultipleObjects: function() {
|
||||
let isPropertyPanelVisible = this.isPropertyPanelVisible();
|
||||
if (isPropertyPanelVisible === true && $('button.delete_multiple_cascade').length > 0) {
|
||||
$('button.delete_multiple_cascade').click();
|
||||
}
|
||||
},
|
||||
isPropertyPanelVisible: function() {
|
||||
let isPanelVisible = false;
|
||||
_.each(pgAdmin.Browser.docker.findPanels(), (panel) => {
|
||||
if (panel._type === 'properties')
|
||||
isPanelVisible = panel.isVisible();
|
||||
});
|
||||
return isPanelVisible;
|
||||
},
|
||||
getTreeDetails: function() {
|
||||
const aciTree = pgAdmin.Browser.tree;
|
||||
const selectedTreeNode = aciTree.selected().length > 0 ? aciTree.selected() : aciTree.first();
|
||||
|
||||
Reference in New Issue
Block a user