',
].join('\n')),
'content': _.template(
' '
),
},
collapse: true,
render: function() {
this.cleanup();
var m = this.model,
$el = this.$el,
tmpl = this.template,
controls = this.controls,
data = {
'className': _.result(this, 'className'),
'legendClass': _.result(this, 'legendClass'),
'contentClass': _.result(this, 'contentClass'),
'collapse': _.result(this, 'collapse'),
},
idx = (this.tabIndex * 100),
evalF = function(f, d, model) {
return (_.isFunction(f) ? !!f.apply(d, [model]) : !!f);
};
this.$el.empty();
_.each(this.schema, function(o) {
idx++;
if (!o.version_compatible || !evalF(o.visible, o, m)) {
return;
}
if (!o.fields)
return;
var d = _.extend({}, data, o),
h = $((tmpl['header'])(d)).appendTo($el),
el = $((tmpl['content'])(d)).appendTo(h);
o.fields.each(function(f) {
var cntr = new(f.get('control'))({
field: f,
model: m,
tabIndex: idx,
});
el.append(cntr.render().$el);
controls.push(cntr);
});
});
return this;
},
getValueFromDOM: function() {
return '';
},
events: {},
});
Backform.Fieldset = Backform.Dialog.extend({
className: function() {
return 'set-group pg-el-12';
},
tabPanelClassName: function() {
return Backform.tabClassName;
},
fieldsetClass: Backform.setGroupClassName,
legendClass: 'badge',
contentClass: Backform.setGroupContentClassName + ' collapse show',
template: {
'header': _.template([
'',
].join('\n')),
'content': _.template(
' '
),
},
collapse: true,
render: function() {
this.cleanup();
var m = this.model,
$el = this.$el,
tmpl = this.template,
controls = this.controls,
data = {
'className': _.result(this, 'className'),
'fieldsetClass': _.result(this, 'fieldsetClass'),
'legendClass': _.result(this, 'legendClass'),
'contentClass': _.result(this, 'contentClass'),
'collapse': _.result(this, 'collapse'),
},
idx = (this.tabIndex * 100),
evalF = function(f, d, model) {
return (_.isFunction(f) ? !!f.apply(d, [model]) : !!f);
};
this.$el.empty();
_.each(this.schema, function(o) {
idx++;
if (!o.version_compatible || !evalF(o.visible, o, m)) {
return;
}
if (!o.fields)
return;
var d = _.extend({}, data, o),
h = $((tmpl['header'])(d)).appendTo($el),
el = $((tmpl['content'])(d)).appendTo(h);
o.fields.each(function(f) {
var cntr = new(f.get('control'))({
field: f,
model: m,
tabIndex: idx,
});
el.append(cntr.render().$el);
controls.push(cntr);
});
});
return this;
},
getValueFromDOM: function() {
return '';
},
events: {},
});
Backform.generateGridColumnsFromModel =
function(node_info, m, type, cols, node) {
var groups = Backform.generateViewSchema(
node_info, m, type, node, true, true
),
schema = [],
columns = [],
func,
idx = 0;
// Create another array if cols is of type object & store its keys in that array,
// If cols is object then chances that we have custom width class attached with in.
if (_.isNull(cols) || _.isUndefined(cols)) {
func = function(f) {
f.cell_priority = idx;
idx = idx + 1;
// We can also provide custom header cell class in schema itself,
// But we will give priority to extraClass attached in cols
// If headerCell property is already set by cols then skip extraClass property from schema
if (!(f.headerCell) && f.cellHeaderClasses) {
f.headerCell = Backgrid.Extension.CustomHeaderCell;
}
};
} else if (_.isArray(cols)) {
func = function(f) {
f.cell_priority = _.indexOf(cols, f.name);
// We can also provide custom header cell class in schema itself,
// But we will give priority to extraClass attached in cols
// If headerCell property is already set by cols then skip extraClass property from schema
if ((!f.headerCell) && f.cellHeaderClasses) {
f.headerCell = Backgrid.Extension.CustomHeaderCell;
}
};
} else if (_.isObject(cols)) {
var tblCols = Object.keys(cols);
func = function(f) {
var val = (f.name in cols) && cols[f.name],
i;
if (_.isNull(val) || _.isUndefined(val)) {
f.cell_priority = -1;
return;
}
if (_.isObject(val)) {
if ('index' in val) {
f.cell_priority = val['index'];
idx = (idx > val['index']) ? idx + 1 : val['index'];
} else {
i = _.indexOf(tblCols, f.name);
f.cell_priority = idx = ((i > idx) ? i : idx);
idx = idx + 1;
}
// We can also provide custom header cell class in schema itself,
// But we will give priority to extraClass attached in cols
// If headerCell property is already set by cols then skip extraClass property from schema
if (!f.headerCell) {
if (f.cellHeaderClasses) {
f.headerCell = Backgrid.Extension.CustomHeaderCell;
}
if ('class' in val && _.isString(val['class'])) {
f.headerCell = Backgrid.Extension.CustomHeaderCell;
f.cellHeaderClasses = (f.cellHeaderClasses || '') + ' ' + val['class'];
}
}
}
if (_.isString(val)) {
i = _.indexOf(tblCols, f.name);
f.cell_priority = idx = ((i > idx) ? i : idx);
idx = idx + 1;
if (!f.headerCell) {
f.headerCell = Backgrid.Extension.CustomHeaderCell;
}
f.cellHeaderClasses = (f.cellHeaderClasses || '') + ' ' + val;
}
};
}
// Prepare columns for backgrid
_.each(groups, function(group) {
_.each(group.fields, function(f) {
if (!f.cell) {
return;
}
// Check custom property in cols & if it is present then attach it to current cell
func(f);
if (f.cell_priority != -1) {
columns.push(f);
}
});
schema.push(group);
});
return {
'columns': _.sortBy(columns, function(c) {
return c.cell_priority;
}),
'schema': schema,
};
};
Backform.BinaryPathsGridControl = Backform.Control.extend({
initialize: function() {
Backform.Control.prototype.initialize.apply(this, arguments);
var BinaryPathModel = Backbone.Model.extend({
idAttribute: 'serverType',
defaults: {
serverType: undefined,
binaryPath: undefined,
isDefault: false
},
});
this.gridColumns = [{
name: 'isDefault',
label: gettext('Set as default'),
sortable: false,
cell: Backgrid.RadioCell,
cellHeaderClasses: 'width_percent_10',
headerCell: Backgrid.Extension.CustomHeaderCell,
deps: ['binaryPath'],
editable: function(m) {
if (!_.isUndefined(m.get('binaryPath')) && !_.isNull(m.get('binaryPath')) && m.get('binaryPath') !== '') {
return true;
} else if (!_.isUndefined(m.get('isDefault')) && !_.isNull(m.get('isDefault'))){
setTimeout(function() {
m.set('isDefault', false);
}, 10);
}
return false;
}
}, {
name: 'serverType',
label: gettext('Database Server'),
editable: false,
cell: 'string',
cellHeaderClasses: 'width_percent_20',
headerCell: Backgrid.Extension.CustomHeaderCell,
}, {
name: 'binaryPath',
label: gettext('Binary Path'),
sortable: false,
cell: Backgrid.Extension.SelectFileCell,
dialog_type: 'select_folder',
dialog_title: gettext('Select Folder'),
placeholder: pgAdmin.server_mode === 'False' ? gettext('Select binary path...') : pgAdmin.enable_binary_path_browsing ? gettext('Select binary path...') : gettext('Enter binary path...'),
browse_btn_label: gettext('Select path'),
check_btn_label: gettext('Validate utilities'),
browse_btn_visible: pgAdmin.server_mode === 'False' ? true : pgAdmin.enable_binary_path_browsing ? true : false
}];
var BinPathCollection = this.BinPathCollection = new (Backbone.Collection.extend({
model: BinaryPathModel
}))(null);
let bin_value = JSON.parse(this.model.get(this.field.get('name')));
this.BinPathCollection.add(bin_value);
this.listenTo(BinPathCollection, 'change', this.binPathCollectionChanged);
},
render: function() {
var self = this,
gridHeader = ['
',
' ' + gettext(this.field.get('label')) + '',
'
',
].join('\n'),
gridBody = $('').append(gridHeader);
self.grid = new Backgrid.Grid({
columns: self.gridColumns,
collection: self.BinPathCollection,
className: 'backgrid table presentation table-bordered table-noouter-border table-hover',
});
this.$el.empty();
this.$el.append(gridBody.append(self.grid.render().$el));
this.$el.append(['' +
gettext('Enter the directory in which the psql, pg_dump, pg_dumpall, and pg_restore' +
' utilities can be found for the corresponding database server version.' +
' The default path will be used for server versions that do not have a' +
' path specified.') + ''].join('\n'));
return this;
},
binPathCollectionChanged: function() {
let bin_value = JSON.stringify(this.BinPathCollection.toJSON());
this.model.set(this.field.get('name'), bin_value, {
silent: false
});
}
});
Backform.UniqueColCollectionControl = Backform.Control.extend({
initialize: function() {
Backform.Control.prototype.initialize.apply(this, arguments);
var uniqueCol = this.field.get('uniqueCol') || [],
m = this.field.get('model'),
schema = m.prototype.schema || m.__super__.schema,
columns = [],
self = this;
_.each(schema, function(s) {
columns.push(s.id);
});
// Check if unique columns provided are also in model attributes.
if (uniqueCol.length > _.intersection(columns, uniqueCol).length) {
var errorMsg = 'Developer: Unique columns [ ' + _.difference(uniqueCol, columns) + ' ] not found in collection model [ ' + columns + ' ].';
throw errorMsg;
}
var collection = self.collection = self.model.get(self.field.get('name'));
if (!collection) {
collection = self.collection = new(pgAdmin.Browser.Node.Collection)(
null, {
model: self.field.get('model'),
silent: true,
handler: self.model,
top: self.model.top || self.model,
attrName: self.field.get('name'),
});
self.model.set(self.field.get('name'), collection, {
silent: true,
});
}
if (this.field.get('version_compatible')) {
self.listenTo(collection, 'add', self.collectionChanged);
self.listenTo(collection, 'change', self.collectionChanged);
}
},
cleanup: function() {
this.stopListening(this.collection, 'change', this.collectionChanged);
if (this.field.get('version_compatible')) {
this.stopListening(self.collection, 'add', this.collectionChanged);
this.stopListening(self.collection, 'change', this.collectionChanged);
}
if (this.grid) {
this.grid.remove();
delete this.grid;
}
this.$el.empty();
},
collectionChanged: function(newModel, coll, op) {
var uniqueCol = this.field.get('uniqueCol') || [],
uniqueChangedAttr = [],
self = this;
// Check if changed model attributes are also in unique columns. And then only check for uniqueness.
if (newModel.attributes) {
_.each(uniqueCol, function(col) {
if (_.has(newModel.attributes, col)) {
uniqueChangedAttr.push(col);
}
});
if (uniqueChangedAttr.length == 0) {
return;
}
} else {
return;
}
var collection = this.model.get(this.field.get('name'));
this.stopListening(collection, 'change', this.collectionChanged);
// Check if changed attribute's value of new/updated model also exist for another model in collection.
// If duplicate value exists then set the attribute's value of new/updated model to its previous values.
var m = undefined,
oldModel = undefined;
collection.each(function(model) {
if (newModel != model) {
var duplicateAttrValues = [];
_.each(uniqueCol, function(attr) {
var attrValue = newModel.get(attr);
if (!_.isUndefined(attrValue) && attrValue == model.get(attr)) {
duplicateAttrValues.push(attrValue);
}
});
if (duplicateAttrValues.length == uniqueCol.length) {
m = newModel;
// Keep reference of model to make it visible in dialog.
oldModel = model;
}
}
});
if (m) {
if (op && op.add) {
// Remove duplicate model.
setTimeout(function() {
collection.remove(m);
}, 0);
} else {
/*
* Set model value to its previous value as its new value is
* conflicting with another model value.
*/
m.set(uniqueChangedAttr[0], m.previous(uniqueChangedAttr[0]));
}
if (oldModel) {
var idx = collection.indexOf(oldModel);
if (idx > -1) {
var newRow = self.grid.body.rows[idx].$el;
newRow.addClass('new');
$(newRow).pgMakeVisible('backform-tab');
setTimeout(function() {
newRow.removeClass('new');
}, 3000);
}
}
}
this.listenTo(collection, 'change', this.collectionChanged);
},
render: function() {
// Clean up existing elements
this.undelegateEvents();
this.$el.empty();
var field = _.defaults(this.field.toJSON(), this.defaults),
attributes = this.model.toJSON(),
attrArr = field.name.split('.'),
name = attrArr.shift(),
path = attrArr.join('.'),
rawValue = this.keyPathAccessor(attributes[name], path),
data = _.extend(field, {
rawValue: rawValue,
value: this.formatter.fromRaw(rawValue, this.model),
attributes: attributes,
formatter: this.formatter,
}),
evalF = function(f, d, m) {
return (_.isFunction(f) ? !!f.apply(d, [m]) : !!f);
};
// Evaluate the disabled, visible, required, canAdd, & canDelete option
_.extend(data, {
disabled: (field.version_compatible &&
evalF.apply(this.field, [data.disabled, data, this.model])
),
visible: evalF.apply(this.field, [data.visible, data, this.model]),
required: evalF.apply(this.field, [data.required, data, this.model]),
canAdd: (field.version_compatible &&
evalF.apply(this.field, [data.canAdd, data, this.model])
),
canAddRow: data.canAddRow,
canDelete: evalF.apply(this.field, [data.canDelete, data, this.model]),
canEdit: evalF.apply(this.field, [data.canEdit, data, this.model]),
});
_.extend(data, {
add_label: '',
});
// This control is not visible, we should remove it.
if (!data.visible) {
return this;
}
this.control_data = _.clone(data);
// Show Backgrid Control
var grid = this.showGridControl(data);
this.$el.html(grid).addClass(field.name);
this.updateInvalid();
this.delegateEvents();
return this;
},
showGridControl: function(data) {
data.cId = data.cId || _.uniqueId('pgC_');
var self = this,
gridHeader = _.template([
'
',
' <%-label%>',
' ',
'
',
].join('\n')),
gridBody = $('').append(
gridHeader(data)
);
// Clean up existing grid if any (in case of re-render)
if (self.grid) {
self.grid.remove();
}
if (!(data.subnode)) {
return '';
}
var subnode = data.subnode.schema ? data.subnode : data.subnode.prototype,
gridSchema = Backform.generateGridColumnsFromModel(
data.node_info, subnode, this.field.get('mode'), data.columns
);
// Set visibility of Add button
if (data.mode == 'properties') {
$(gridBody).find('button.add').remove();
}
// Insert Delete Cell into Grid
if (!data.disabled && data.canDelete) {
gridSchema.columns.unshift({
name: 'pg-backform-delete',
label: '',
cell: Backgrid.Extension.DeleteCell,
editable: false,
cell_priority: -1,
canDeleteRow: data.canDeleteRow,
});
}
// Insert Edit Cell into Grid
if (data.disabled == false && data.canEdit) {
var editCell = Backgrid.Extension.ObjectCell.extend({
schema: gridSchema.schema,
});
gridSchema.columns.unshift({
name: 'pg-backform-edit',
label: '',
cell: editCell,
cell_priority: -2,
canEditRow: data.canEditRow,
});
}
var collection = this.model.get(data.name);
var cellEditing = function(args) {
var that = this,
cell = args[0];
// Search for any other rows which are open.
this.each(function(m) {
// Check if row which we are about to close is not current row.
if (cell.model != m) {
var idx = that.indexOf(m);
if (idx > -1) {
var row = self.grid.body.rows[idx],
rowEditCell = row.$el.find('.subnode-edit-in-process').parent();
// Only close row if it's open.
if (rowEditCell.length > 0) {
var event = new Event('click');
rowEditCell[0].dispatchEvent(event);
}
}
}
});
};
// Listen for any row which is about to enter in edit mode.
collection.on('enteringEditMode', cellEditing, collection);
// Initialize a new Grid instance
self.grid = new Backgrid.Grid({
columns: gridSchema.columns,
collection: collection,
className: 'backgrid table presentation table-bordered table-noouter-border table-hover',
attr: {
'aria-labelledby': data.cId,
},
});
for(let i = 0; i < (collection.length); i++) {
collection.at(i).parentTr = self.grid.body.rows[i].$el;
}
// Render subNode grid
var subNodeGrid = self.grid.render().$el;
// Combine Edit and Delete Cell
if (data.canDelete && data.canEdit) {
$(subNodeGrid).find('th.pg-backform-delete').remove();
$(subNodeGrid).find('th.pg-backform-edit').attr('colspan', '2');
}
var $dialog = gridBody.append(subNodeGrid);
let tmp_browser = pgBrowser;
if (pgBrowser.preferences_cache.length == 0)
tmp_browser = pgWindow.default.pgAdmin.Browser;
let preferences = tmp_browser.get_preferences_for_module('browser');
if (preferences) {
let addBtn = $dialog.find('.add');
// Add title to the buttons
$(addBtn)
.attr('title',
keyboardShortcuts.shortcut_title(gettext('Add new row'),preferences.add_grid_row));
}
// Add button callback
if (!(data.disabled || data.canAdd == false)) {
$dialog.find('button.add').first().on('click',(e) => {
e.preventDefault();
var canAddRow = _.isFunction(data.canAddRow) ?
data.canAddRow.apply(self, [self.model]) : true;
if (canAddRow) {
// Close any existing expanded row before adding new one.
_.each(self.grid.body.rows, function(row) {
var rowEditCell = row.$el.find('.subnode-edit-in-process').parent();
// Only close row if it's open.
if (rowEditCell.length > 0) {
var event = new Event('click');
rowEditCell[0].dispatchEvent(event);
}
});
var allowMultipleEmptyRows = !!self.field.get('allowMultipleEmptyRows');
// If allowMultipleEmptyRows is not set or is false then don't allow second new empty row.
// There should be only one empty row.
if (!allowMultipleEmptyRows && collection) {
var isEmpty = false;
collection.each(function(model) {
var modelValues = [];
_.each(model.attributes, function(val) {
modelValues.push(val);
});
if (!_.some(modelValues, _.identity)) {
isEmpty = true;
}
});
if (isEmpty) {
return false;
}
}
$(self.grid.body.$el.find($('tr.new'))).removeClass('new');
var m = new(data.model)(null, {
silent: true,
handler: collection,
top: self.model.top || self.model,
collection: collection,
node_info: self.model.node_info,
});
if(data.beforeAdd) {
m = data.beforeAdd.apply(self, [m]);
}
collection.add(m);
var idx = collection.indexOf(m),
newRow = self.grid.body.rows[idx].$el;
collection.get(m).parentTr = newRow;
m.parentTr = newRow;
newRow.addClass('new');
if(!$(newRow).pgMakeBackgridVisible('.backform-tab')){
// We can have subnode controls in Panels
$(newRow).pgMakeBackgridVisible('.set-group');
}
return false;
}
});
}
return $dialog;
},
clearInvalid: function() {
this.$el.removeClass('subnode-error');
this.$el.find('.pgadmin-control-error-message').remove();
return this;
},
updateInvalid: function() {
var self = this,
errorModel = self.model.errorModel;
if (!(errorModel instanceof Backbone.Model)) return this;
this.clearInvalid();
},
});
Backform.SubNodeCollectionControl = Backform.Control.extend({
row: Backgrid.Row,
render: function() {
var field = _.defaults(this.field.toJSON(), this.defaults),
attributes = this.model.toJSON(),
attrArr = field.name.split('.'),
name = attrArr.shift(),
path = attrArr.join('.'),
rawValue = this.keyPathAccessor(attributes[name], path),
data = _.extend(field, {
rawValue: rawValue,
value: this.formatter.fromRaw(rawValue, this.model),
attributes: attributes,
formatter: this.formatter,
}),
evalF = function(f, d, m) {
return (_.isFunction(f) ? !!f.apply(d, [m]) : !!f);
};
// Evaluate the disabled, visible, required, canAdd, cannEdit & canDelete option
_.extend(data, {
disabled: evalF(data.disabled, data, this.model),
visible: evalF(data.visible, data, this.model),
required: evalF(data.required, data, this.model),
canAdd: evalF(data.canAdd, data, this.model),
canAddRow: data.canAddRow,
canEdit: evalF(data.canEdit, data, this.model),
canDelete: evalF(data.canDelete, data, this.model),
showError: data.showError || true,
});
// Show Backgrid Control
var grid = (data.subnode == undefined) ? '' : this.showGridControl(data);
// Clean up first
this.$el.removeClass(Backform.hiddenClassName);
if (!data.visible)
this.$el.addClass(Backform.hiddenClassName);
this.$el.html(grid).addClass(field.name);
this.updateInvalid();
return this;
},
updateInvalid: function() {
var self = this;
var errorModel = this.model.errorModel;
if (!(errorModel instanceof Backbone.Model)) return this;
this.clearInvalid();
var attrArr = self.field.get('name').split('.'),
path = attrArr.join('.'),
error = self.keyPathAccessor(errorModel.toJSON(), path);
if (_.isEmpty(error)) return;
if (self.field.get('showError')) {
self.$el.addClass('subnode-error').append(
$('').addClass('pgadmin-control-error-message pg-el-offset-4 pg-el-8 help-block').text(error)
);
}
},
cleanup: function() {
// Clean up existing grid if any (in case of re-render)
if (this.grid) {
this.grid.remove();
}
if (this.collection) {
this.collection.off('enteringEditMode');
}
},
clearInvalid: function() {
if (this.field.get('showError')) {
this.$el.removeClass('subnode-error');
this.$el.find('.pgadmin-control-error-message').remove();
}
return this;
},
showGridControl: function(data) {
var self = this,
gridHeader = ['
',
' ' + data.label + '',
' ',
'
',
].join('\n'),
gridBody = $('').append(gridHeader);
var subnode = data.subnode.schema ? data.subnode : data.subnode.prototype,
gridSchema = Backform.generateGridColumnsFromModel(
data.node_info, subnode, this.field.get('mode'), data.columns, data.schema_node
);
// Clean up existing grid if any (in case of re-render)
if (self.grid) {
self.grid.remove();
}
// Set visibility of Add button
if (data.disabled || data.canAdd == false) {
$(gridBody).find('button.add').remove();
}
// Insert Delete Cell into Grid
if (data.disabled == false && data.canDelete) {
gridSchema.columns.unshift({
name: 'pg-backform-delete',
label: '',
cell: Backgrid.Extension.DeleteCell,
editable: false,
cell_priority: -1,
canDeleteRow: data.canDeleteRow,
customDeleteMsg: data.customDeleteMsg,
customDeleteTitle: data.customDeleteTitle,
});
}
// Insert Edit Cell into Grid
if (data.disabled == false && data.canEdit) {
var editCell = Backgrid.Extension.ObjectCell.extend({
schema: gridSchema.schema,
}),
canEdit = self.field.has('canEdit') &&
self.field.get('canEdit') || true;
gridSchema.columns.unshift({
name: 'pg-backform-edit',
label: '',
cell: editCell,
cell_priority: -2,
editable: canEdit,
canEditRow: data.canEditRow,
});
}
var collection = self.model.get(data.name);
if (!collection) {
collection = new(pgBrowser.Node.Collection)(null, {
handler: self.model.handler || self.model,
model: data.model,
top: self.model.top || self.model,
silent: true,
});
self.model.set(data.name, collection, {
silent: true,
});
}
var cellEditing = function(args) {
var ctx = this,
cell = args[0];
// Search for any other rows which are open.
this.each(function(m) {
// Check if row which we are about to close is not current row.
if (cell.model != m) {
var idx = ctx.indexOf(m);
if (idx > -1) {
var row = grid.body.rows[idx],
rowEditCell = row.$el.find('.subnode-edit-in-process').parent();
// Only close row if it's open.
if (rowEditCell.length > 0) {
var event = new Event('click');
rowEditCell[0].dispatchEvent(event);
}
}
}
});
};
// Listen for any row which is about to enter in edit mode.
collection.on('enteringEditMode', cellEditing, collection);
// Initialize a new Grid instance
var grid = self.grid = new Backgrid.Grid({
columns: gridSchema.columns,
collection: collection,
row: this.row,
className: 'backgrid table presentation table-bordered table-noouter-border table-hover',
});
// Render subNode grid
var subNodeGrid = grid.render().$el;
// Combine Edit and Delete Cell
if (data.canDelete && data.canEdit) {
$(subNodeGrid).find('th.pg-backform-delete').remove();
$(subNodeGrid).find('th.pg-backform-edit').attr('colspan', '2');
}
var $dialog = gridBody.append(subNodeGrid);
let tmp_browser = pgBrowser;
if (pgBrowser.preferences_cache.length == 0)
tmp_browser = pgWindow.default.pgAdmin.Browser;
let preferences = tmp_browser.get_preferences_for_module('browser');
if (preferences) {
let addBtn = $dialog.find('.add');
// Add title to the buttons
$(addBtn)
.attr('title',
keyboardShortcuts.shortcut_title(gettext('Add new row'),preferences.add_grid_row));
}
// Add button callback
$dialog.find('button.add').on('click',(e) => {
e.preventDefault();
var canAddRow = _.isFunction(data.canAddRow) ?
data.canAddRow.apply(self, [self.model]) : true;
if (canAddRow) {
// Close any existing expanded row before adding new one.
_.each(grid.body.rows, function(row) {
var rowEditCell = row.$el.find('.subnode-edit-in-process').parent();
// Only close row if it's open.
if (rowEditCell.length > 0) {
var event = new Event('click');
rowEditCell[0].dispatchEvent(event);
}
});
grid.insertRow({});
var newRow = $(grid.body.rows[collection.length - 1].$el);
newRow.attr('class', 'new').on('click',() => {
$(this).attr('class', 'editable');
});
if(!$(newRow).pgMakeBackgridVisible('.backform-tab')){
// We can have subnode controls in Panels
$(newRow).pgMakeBackgridVisible('.set-group');
}
return false;
}
});
return $dialog;
},
});
/*
* SQL Tab Control for showing the modified SQL for the node with the
* property 'hasSQL' is set to true.
*
* When the user clicks on the SQL tab, we will send the modified data to the
* server and fetch the SQL for it.
*/
Backform.SqlTabControl = Backform.Control.extend({
defaults: {
label: '',
controlsClassName: 'pgadmin-controls pg-el-sm-12 SQL',
extraClasses: [],
helpMessage: null,
},
template: _.template([
'',
'