mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Introduced the VariableControl, which will be used in role(s),
database(s), and tablespace(s). Thanks Harshal Dhumal for sharing the original patch. I've modified a lot to work by fetching the variables later by giving a url. Also, - Introduced the template macros for SECURITY LABELS and VARIABLES. - Improvised the Backform.Control with better syntactic approach. - Introduced a jquery function pgMakeVisible(..) to make it visible under any obj which can be identified by unique class.
This commit is contained in:
@@ -97,6 +97,11 @@ class ServerModule(sg.ServerGroupPluginModule):
|
|||||||
'name': 'pgadmin.browser.server.privilege',
|
'name': 'pgadmin.browser.server.privilege',
|
||||||
'path': url_for('browser.index') + 'server/static/js/privilege',
|
'path': url_for('browser.index') + 'server/static/js/privilege',
|
||||||
'when': self.node_type
|
'when': self.node_type
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'pgadmin.browser.server.variable',
|
||||||
|
'path': url_for('browser.index') + 'server/static/js/variable',
|
||||||
|
'when': self.node_type
|
||||||
}])
|
}])
|
||||||
|
|
||||||
for module in self.submodules:
|
for module in self.submodules:
|
||||||
|
|||||||
562
web/pgadmin/browser/server_groups/servers/static/js/variable.js
Normal file
562
web/pgadmin/browser/server_groups/servers/static/js/variable.js
Normal file
@@ -0,0 +1,562 @@
|
|||||||
|
(function(root, factory) {
|
||||||
|
// Set up Backform appropriately for the environment. Start with AMD.
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
define([
|
||||||
|
'underscore', 'jquery', 'backbone', 'backform', 'backgrid', 'alertify',
|
||||||
|
'pgadmin', 'pgadmin.browser.node', 'pgadmin.browser.node.ui'
|
||||||
|
],
|
||||||
|
function(_, $, Backbone, Backform, Backgrid, Alertify, pgAdmin, pgNode) {
|
||||||
|
// Export global even in AMD case in case this script is loaded with
|
||||||
|
// others that may still expect a global Backform.
|
||||||
|
return factory(root, _, $, Backbone, Backform, Alertify, pgAdmin, pgNode);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Next for Node.js or CommonJS. jQuery may not be needed as a module.
|
||||||
|
} else if (typeof exports !== 'undefined') {
|
||||||
|
var _ = require('underscore') || root._,
|
||||||
|
$ = root.jQuery || root.$ || root.Zepto || root.ender,
|
||||||
|
Backbone = require('backbone') || root.Backbone,
|
||||||
|
Backform = require('backform') || root.Backform;
|
||||||
|
Alertify = require('alertify') || root.Alertify;
|
||||||
|
pgAdmin = require('pgadmin') || root.pgAdmin,
|
||||||
|
pgNode = require('pgadmin.browser.node') || root.pgAdmin.Browser.Node;
|
||||||
|
factory(root, _, $, Backbone, Backform, Alertify, pgAdmin, pgNode);
|
||||||
|
|
||||||
|
// Finally, as a browser global.
|
||||||
|
} else {
|
||||||
|
factory(
|
||||||
|
root, root._, (root.jQuery || root.Zepto || root.ender || root.$),
|
||||||
|
root.Backbone, root.Backform, root.pgAdmin.Browser.Node
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} (this, function(root, _, $, Backbone, Backform, Alertify, pgAdmin, pgNode) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VariableModel used to represent configuration parameters (variables tab)
|
||||||
|
* for database objects.
|
||||||
|
**/
|
||||||
|
var VariableModel = pgNode.VariableModel = pgNode.Model.extend({
|
||||||
|
defaults: {
|
||||||
|
name: undefined,
|
||||||
|
value: undefined,
|
||||||
|
role: undefined,
|
||||||
|
database: undefined,
|
||||||
|
},
|
||||||
|
schema: [
|
||||||
|
{id: 'name', label:'Name', type:'text', editable: false, cellHeaderClasses: 'width_percent_30'},
|
||||||
|
{
|
||||||
|
id: 'value', label:'Value', type: 'text', cell: 'dynamic-variable',
|
||||||
|
editable: true, cellHeaderClasses: 'width_percent_50'
|
||||||
|
},
|
||||||
|
{id: 'database', label:'Database', type: 'text', editable: false},
|
||||||
|
{id: 'role', label:'Role', type: 'text', editable: false}
|
||||||
|
],
|
||||||
|
toJSON: function() {
|
||||||
|
var d = Backbone.Model.prototype.toJSON.apply(this);
|
||||||
|
|
||||||
|
// Remove not defined values from model values.
|
||||||
|
// i.e.
|
||||||
|
// role, database
|
||||||
|
if (_.isUndefined(d.database)) {
|
||||||
|
delete d.database;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isUndefined(d.role) || _.isNull(d.role)) {
|
||||||
|
delete d.role;
|
||||||
|
}
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dynamic Variable cell. Used for variable data type column in Variables tab.
|
||||||
|
* Behaviour of cell depends on variable data type.
|
||||||
|
*/
|
||||||
|
var DynamicVariableCell = Backgrid.Extension.DynamicVariableCell = Backgrid.Cell.extend({
|
||||||
|
/*
|
||||||
|
* Mapping of postgres data type to backgrid cell type.
|
||||||
|
*/
|
||||||
|
variableCellMapper: {
|
||||||
|
"bool":Backgrid.Extension.SwitchCell,
|
||||||
|
"enum":Backgrid.Extension.Select2Cell,
|
||||||
|
"string":Backgrid.Cell,
|
||||||
|
"integer":Backgrid.IntegerCell,
|
||||||
|
"real":Backgrid.NumberCell
|
||||||
|
},
|
||||||
|
initialize: function (opts) {
|
||||||
|
|
||||||
|
var self = this,
|
||||||
|
name = opts.model.get("name");
|
||||||
|
self.availVariables = opts.column.get('availVariables');
|
||||||
|
|
||||||
|
var variable = (self.availVariables[name]),
|
||||||
|
cell = self.variableCellMapper[variable.vartype] || Backgrid.Cell;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set properties for dynamic cell.
|
||||||
|
*/
|
||||||
|
_.each(cell.prototype, function(v,k) {
|
||||||
|
self[k] = v;
|
||||||
|
});
|
||||||
|
|
||||||
|
DynamicVariableCell.__super__.initialize.apply(self, arguments);
|
||||||
|
|
||||||
|
switch(variable.vartype) {
|
||||||
|
case "bool":
|
||||||
|
// There are no specific properties for BooleanCell.
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "enum":
|
||||||
|
var options = [],
|
||||||
|
name = self.model.get("name"),
|
||||||
|
enumVals = variable.enumvals;
|
||||||
|
|
||||||
|
_.each(enumVals, function(enumVal) {
|
||||||
|
options.push([enumVal, enumVal]);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.optionValues = options;
|
||||||
|
self.multiple = cell.prototype.multiple;
|
||||||
|
self.delimiter = cell.prototype.delimiter;
|
||||||
|
|
||||||
|
self.listenTo(
|
||||||
|
self.model, "backgrid:edit",
|
||||||
|
function (model, column, cell, editor) {
|
||||||
|
if (column.get("name") == self.column.get("name")) {
|
||||||
|
editor.setOptionValues(self.optionValues);
|
||||||
|
editor.setMultiple(self.multiple);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "integer":
|
||||||
|
|
||||||
|
self.decimals = 0;
|
||||||
|
self.decimalSeparator = cell.prototype.decimalSeparator;
|
||||||
|
self.orderSeparator = cell.prototype.orderSeparator;
|
||||||
|
var formatter = self.formatter;
|
||||||
|
|
||||||
|
formatter.decimals = self.decimals;
|
||||||
|
formatter.decimalSeparator = self.decimalSeparator;
|
||||||
|
formatter.orderSeparator = self.orderSeparator;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "real":
|
||||||
|
|
||||||
|
self.decimals = cell.prototype.decimals;
|
||||||
|
self.decimalSeparator = cell.prototype.decimalSeparator;
|
||||||
|
self.orderSeparator = cell.prototype.orderSeparator;
|
||||||
|
|
||||||
|
var formatter = self.formatter;
|
||||||
|
|
||||||
|
formatter.decimals = self.decimals;
|
||||||
|
formatter.decimalSeparator = self.decimalSeparator;
|
||||||
|
formatter.orderSeparator = self.orderSeparator;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "string":
|
||||||
|
default:
|
||||||
|
// There are no specific properties for StringCell and Cell.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable Tab Control to set/update configuration values for database object.
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
var VariableCollectionControl = Backform.VariableCollectionControl =
|
||||||
|
Backform.UniqueColCollectionControl.extend({
|
||||||
|
|
||||||
|
hasDatabase: false,
|
||||||
|
hasRole: false,
|
||||||
|
|
||||||
|
defaults: _.extend({
|
||||||
|
uniqueCol: ['name', 'role', 'database']
|
||||||
|
},
|
||||||
|
Backform.UniqueColCollectionControl.prototype.defaults
|
||||||
|
),
|
||||||
|
|
||||||
|
initialize: function(opts) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Overriding the uniqueCol in the field
|
||||||
|
if (opts && opts.field) {
|
||||||
|
if (opts.field instanceof Backform.Field) {
|
||||||
|
opts.field.set({
|
||||||
|
uniqueCol: ['name', 'role', 'database'],
|
||||||
|
model: pgNode.VariableModel
|
||||||
|
},
|
||||||
|
{
|
||||||
|
silent: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
opts.field.extend({
|
||||||
|
uniqueCol: ['name', 'role', 'database'],
|
||||||
|
model: pgNode.VariableModel
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Backform.UniqueColCollectionControl.prototype.initialize.apply(
|
||||||
|
self, arguments
|
||||||
|
);
|
||||||
|
|
||||||
|
self.hasDatabase = self.field.get('hasDatabase');
|
||||||
|
self.hasRole = self.field.get('hasRole');
|
||||||
|
self.availVariables = {};
|
||||||
|
|
||||||
|
var node = self.field.get('node').type,
|
||||||
|
headerSchema = [{
|
||||||
|
id: 'name', label:'', type:'text',
|
||||||
|
url: self.field.get('variable_opts') || 'vopts',
|
||||||
|
control: Backform.NodeAjaxOptionsControl,
|
||||||
|
select2: {
|
||||||
|
allowClear: false, width: 'style'
|
||||||
|
},
|
||||||
|
availVariables: self.availVariables,
|
||||||
|
node: node, first_empty: false,
|
||||||
|
transform: function(vars) {
|
||||||
|
var self = this,
|
||||||
|
opts = self.field.get('availVariables');
|
||||||
|
|
||||||
|
res = [];
|
||||||
|
|
||||||
|
for (var prop in opts) {
|
||||||
|
if (opts.hasOwnProperty(prop)) {
|
||||||
|
delete opts[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_.each(vars, function(v) {
|
||||||
|
opts[v.name] = _.extend({}, v);
|
||||||
|
res.push({
|
||||||
|
'label': v.name,
|
||||||
|
'value': v.name
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
headerDefaults = {name: null},
|
||||||
|
gridCols = ['name', 'value'];
|
||||||
|
|
||||||
|
if (self.hasDatabase) {
|
||||||
|
headerSchema.push({
|
||||||
|
id: 'database', label:'', type: 'text',
|
||||||
|
control: Backform.NodeListByNameControl, node: 'database'
|
||||||
|
});
|
||||||
|
headerDefaults['database'] = null;
|
||||||
|
gridCols.push('database');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.hasRole) {
|
||||||
|
headerSchema.push({
|
||||||
|
id: 'role', label:'', type: 'text',
|
||||||
|
control: Backform.NodeListByNameControl, node: 'role'
|
||||||
|
});
|
||||||
|
headerDefaults['role'] = null;
|
||||||
|
gridCols.push('role');
|
||||||
|
}
|
||||||
|
|
||||||
|
self.headerData = new (Backbone.Model.extend({
|
||||||
|
defaults: headerDefaults,
|
||||||
|
schema: headerSchema
|
||||||
|
}))({});
|
||||||
|
|
||||||
|
var headerGroups = Backform.generateViewSchema(
|
||||||
|
self.field.get('node_info'), self.headerData, 'create',
|
||||||
|
node, self.field.get('node_data')
|
||||||
|
);
|
||||||
|
|
||||||
|
var fields = [];
|
||||||
|
|
||||||
|
_.each(headerGroups, function(val, key) {
|
||||||
|
fields = fields.concat(headerGroups[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.headerFields = new Backform.Fields(fields);
|
||||||
|
self.gridSchema = Backform.generateGridColumnsFromModel(
|
||||||
|
null, VariableModel, 'edit', gridCols
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make sure - we do have the data for variables
|
||||||
|
self.getVariables();
|
||||||
|
|
||||||
|
self.controls = [];
|
||||||
|
self.listenTo(self.headerData, "change", self.headerDataChanged);
|
||||||
|
self.listenTo(self.headerData, "select2", self.headerDataChanged);
|
||||||
|
self.listenTo(self.collection, "remove", self.onRemoveVariable);
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the variable options for this control.
|
||||||
|
*/
|
||||||
|
getVariables: function() {
|
||||||
|
var self = this,
|
||||||
|
url = this.field.get('url'),
|
||||||
|
m = self.model;
|
||||||
|
|
||||||
|
if (url && !m.isNew()) {
|
||||||
|
var node = self.field.get('node'),
|
||||||
|
node_data = self.field.get('node_data'),
|
||||||
|
node_info = self.field.get('node_info'),
|
||||||
|
full_url = node.generate_url.apply(
|
||||||
|
node, [
|
||||||
|
null, url, node_data, true, node_info
|
||||||
|
]),
|
||||||
|
data;
|
||||||
|
|
||||||
|
m.trigger('pgadmin:view:fetching', m, self.field);
|
||||||
|
$.ajax({
|
||||||
|
async: false,
|
||||||
|
url: full_url,
|
||||||
|
success: function (res) {
|
||||||
|
data = res.data;
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
m.trigger('pgadmin:view:fetch:error', m, self.field);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
m.trigger('pgadmin:view:fetched', m, self.field);
|
||||||
|
|
||||||
|
if (data && _.isArray(data)) {
|
||||||
|
self.collection.reset(data, {silent: true});
|
||||||
|
/*
|
||||||
|
* Make sure - new data will be taken care by the session management
|
||||||
|
*/
|
||||||
|
self.collection.startNewSession();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
generateHeader: function(data) {
|
||||||
|
var header = [
|
||||||
|
"<div class='subnode-header-form'>",
|
||||||
|
" <div class='container-fluid'>",
|
||||||
|
" <div class='row'>",
|
||||||
|
" <div class='col-md-4'>",
|
||||||
|
" <label class='control-label'>Variable name</label>",
|
||||||
|
" </div>",
|
||||||
|
" <div class='col-md-4' header='name'></div>",
|
||||||
|
" <div class='col-md-4'>",
|
||||||
|
" <button class='btn-sm btn-default add'>Add</buttton>",
|
||||||
|
" </div>",
|
||||||
|
" </div>"];
|
||||||
|
|
||||||
|
if(this.hasDatabase) {
|
||||||
|
header.push([
|
||||||
|
" <div class='row'>",
|
||||||
|
" <div class='col-md-4'>",
|
||||||
|
" <label class='control-label'>Database</label>",
|
||||||
|
" </div>",
|
||||||
|
" <div class='col-md-4' header='database'></div>",
|
||||||
|
" </div>"].join("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasRole) {
|
||||||
|
header.push([
|
||||||
|
" <div class='row'>",
|
||||||
|
" <div class='col-md-4'>",
|
||||||
|
" <label class='control-label'>Role</label>",
|
||||||
|
" </div>",
|
||||||
|
" <div class='col-md-4' header='role'></div>",
|
||||||
|
" </div>"].join("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
header.push([
|
||||||
|
" </div>",
|
||||||
|
"</div>"].join("\n"));
|
||||||
|
|
||||||
|
var self = this,
|
||||||
|
$header = $(header.join("\n")),
|
||||||
|
controls = this.controls;
|
||||||
|
|
||||||
|
this.headerFields.each(function(field) {
|
||||||
|
var control = new (field.get("control"))({
|
||||||
|
field: field,
|
||||||
|
model: self.headerData
|
||||||
|
});
|
||||||
|
|
||||||
|
$header.find('div[header="' + field.get('name') + '"]').append(
|
||||||
|
control.render().$el
|
||||||
|
);
|
||||||
|
|
||||||
|
controls.push(control);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set visibility of Add button
|
||||||
|
if (data.disabled || data.canAdd == false) {
|
||||||
|
$header.find("button.add").remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.$header = $header;
|
||||||
|
|
||||||
|
return $header;
|
||||||
|
},
|
||||||
|
|
||||||
|
events: _.extend(
|
||||||
|
{},
|
||||||
|
Backform.UniqueColCollectionControl.prototype.events,
|
||||||
|
{
|
||||||
|
'click button.add': 'addVariable'
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
showGridControl: function(data) {
|
||||||
|
|
||||||
|
var self = this,
|
||||||
|
titleTmpl = _.template([
|
||||||
|
"<div class='subnode-header'>",
|
||||||
|
"<label class='control-label'><%-label%></label>",
|
||||||
|
"</div>"].join("\n")),
|
||||||
|
$gridBody =
|
||||||
|
$("<div class='pgadmin-control-group backgrid form-group col-xs-12 object subnode'></div>").append(
|
||||||
|
titleTmpl({label: data.label})
|
||||||
|
);
|
||||||
|
|
||||||
|
$gridBody.append(self.generateHeader(data));
|
||||||
|
|
||||||
|
var gridSchema = _.clone(this.gridSchema);
|
||||||
|
|
||||||
|
_.each(gridSchema.columns, function(col) {
|
||||||
|
if (col.name == 'value') {
|
||||||
|
col.availVariables = self.availVariables;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize a new Grid instance
|
||||||
|
var grid = self.grid = new Backgrid.Grid({
|
||||||
|
columns: gridSchema.columns,
|
||||||
|
collection: self.collection,
|
||||||
|
className: "backgrid table-bordered"
|
||||||
|
});
|
||||||
|
self.$grid = grid.render().$el;
|
||||||
|
|
||||||
|
$gridBody.append(self.$grid);
|
||||||
|
|
||||||
|
self.headerData.set(
|
||||||
|
'name',
|
||||||
|
self.$header.find(
|
||||||
|
'div[header="name"] select option:first'
|
||||||
|
).val()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render node grid
|
||||||
|
return $gridBody;
|
||||||
|
},
|
||||||
|
|
||||||
|
addVariable: function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
var self = this,
|
||||||
|
m = new (self.field.get('model'))(
|
||||||
|
self.headerData.toJSON(), {silent: true}
|
||||||
|
),
|
||||||
|
coll = self.model.get(self.field.get('name'));
|
||||||
|
|
||||||
|
coll.add(m);
|
||||||
|
|
||||||
|
var idx = coll.indexOf(m);
|
||||||
|
|
||||||
|
// idx may not be always > -1 because our UniqueColCollection may
|
||||||
|
// remove 'm' if duplicate value found.
|
||||||
|
if (idx > -1) {
|
||||||
|
self.$grid.find('.new').removeClass('new');
|
||||||
|
|
||||||
|
var newRow = self.grid.body.rows[idx].$el;
|
||||||
|
|
||||||
|
newRow.addClass("new");
|
||||||
|
$(newRow).pgMakeVisible('backform-tab');
|
||||||
|
} else {
|
||||||
|
delete m;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.headerDataChanged();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
headerDataChanged: function() {
|
||||||
|
var self = this, val,
|
||||||
|
data = this.headerData.toJSON(),
|
||||||
|
inSelected = false,
|
||||||
|
checkVars = ['name'];
|
||||||
|
|
||||||
|
if (!self.$header) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.hasDatabase) {
|
||||||
|
checkVars.push('database');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.role) {
|
||||||
|
checkVars.push('role');
|
||||||
|
}
|
||||||
|
|
||||||
|
self.collection.each(function(m) {
|
||||||
|
if (!inSelected) {
|
||||||
|
var has = true;
|
||||||
|
_.each(checkVars, function(v) {
|
||||||
|
val = m.get(v);
|
||||||
|
has = has && ((
|
||||||
|
(_.isUndefined(val) || _.isNull(val)) &&
|
||||||
|
(_.isUndefined(data[v]) || _.isNull(data[v]))
|
||||||
|
) ||
|
||||||
|
(val == data[v]));
|
||||||
|
});
|
||||||
|
|
||||||
|
inSelected = has;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.$header.find('button.add').prop('disabled', inSelected);
|
||||||
|
},
|
||||||
|
|
||||||
|
onRemoveVariable: function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Wait for collection to be updated before checking for the button to be
|
||||||
|
// enabled, or not.
|
||||||
|
setTimeout(function() {
|
||||||
|
self.headerDataChanged();
|
||||||
|
}, 10);
|
||||||
|
},
|
||||||
|
|
||||||
|
remove: function() {
|
||||||
|
/*
|
||||||
|
* Stop listening the events registered by this control.
|
||||||
|
*/
|
||||||
|
this.stopListening(this.headerData, "change", this.headerDataChanged);
|
||||||
|
this.listenTo(this.headerData, "select2", this.headerDataChanged);
|
||||||
|
this.listenTo(this.collection, "remove", this.onRemoveVariable);
|
||||||
|
|
||||||
|
VariableCollectionControl.__super__.remove.apply(this, arguments);
|
||||||
|
|
||||||
|
// Remove the header model
|
||||||
|
delete (this.headerData);
|
||||||
|
|
||||||
|
// Clear the available Variables object
|
||||||
|
self.availVariables = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return VariableModel;
|
||||||
|
}));
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{% macro APPLY(conn, type, name, provider, label) -%}
|
||||||
|
SECURITY LABEL FOR {{ conn|qtIdent(provider) }} ON {{ type }} {{ conn|qtIdent(name) }} IS {{ label|qtLiteral }};
|
||||||
|
{%- endmacro %}
|
||||||
|
{% macro DROP(conn, type, name, provider) -%}
|
||||||
|
SECURITY LABEL FOR {{ conn|qtIdent(provider) }} ON {{ type }} {{ conn|qtIdent(name) }} IS NULL;
|
||||||
|
{%- endmacro %}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{% macro APPLY(conn, database, role, param, value) -%}
|
||||||
|
ALTER {% if role %}ROLE {{ self.conn|qtIdent(role) }}{% if database %} IN DATABASE {{ conn|qtIdent(database) }}{% endif %}{% else %}DATABASE {{ conn|qtIdent(database) }}{% endif %} SET {{ conn|qtIdent(param) }} TO {{ value|qtLiteral }};
|
||||||
|
{%- endmacro %}
|
||||||
|
{% macro DROP(conn, database, role, param) -%}
|
||||||
|
ALTER {% if role %}ROLE {{ self.conn|qtIdent(role) }}{% if database %} IN DATABASE {{ conn|qtIdent(database) }}{% endif %}{% else %}DATABASE {{ conn|qtIdent(database) }}{% endif %} RESET {{ conn|qtIdent(param) }};
|
||||||
|
{%- endmacro %}
|
||||||
@@ -4,6 +4,21 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
|
|||||||
|
|
||||||
var pgBrowser = pgAdmin.Browser;
|
var pgBrowser = pgAdmin.Browser;
|
||||||
|
|
||||||
|
|
||||||
|
// Store value in DOM as stringified JSON.
|
||||||
|
var StringOrJSONFormatter = function() {};
|
||||||
|
_.extend(StringOrJSONFormatter.prototype, {
|
||||||
|
fromRaw: function(rawData, model) {
|
||||||
|
return JSON.stringify(rawData);
|
||||||
|
},
|
||||||
|
toRaw: function(formattedData, model) {
|
||||||
|
if (typeof(formattedData) == 'string') {
|
||||||
|
return formattedData;
|
||||||
|
}
|
||||||
|
return JSON.parse(formattedData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NodeAjaxOptionsControl
|
* NodeAjaxOptionsControl
|
||||||
* This control will fetch the options required to render the select
|
* This control will fetch the options required to render the select
|
||||||
@@ -21,8 +36,28 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
|
|||||||
defaults: _.extend(Backform.SelectControl.prototype.defaults, {
|
defaults: _.extend(Backform.SelectControl.prototype.defaults, {
|
||||||
url: undefined,
|
url: undefined,
|
||||||
transform: undefined,
|
transform: undefined,
|
||||||
url_with_id: false
|
url_with_id: false,
|
||||||
|
first_empty: false,
|
||||||
|
select2: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: 'Select from the list',
|
||||||
|
width: 'style'
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
template: _.template([
|
||||||
|
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
|
||||||
|
'<div class="<%=Backform.controlsClassName%> <%=extraClasses.join(\' \')%>">',
|
||||||
|
' <select class="pgadmin-node-select form-control" name="<%=name%>" style="width:100%;" value=<%-value%> <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> >',
|
||||||
|
' <% if (first_empty) { %>',
|
||||||
|
' <option value="" <%="" === rawValue ? "selected" : "" %>><%- empty_value %></option>',
|
||||||
|
' <% } %>',
|
||||||
|
' <% for (var i=0; i < options.length; i++) { %>',
|
||||||
|
' <% var option = options[i]; %>',
|
||||||
|
' <option <% if (option.image) { %> data-image=<%= option.image %> <% } %> value=<%= formatter.fromRaw(option.value) %> <%=option.value === rawValue ? "selected=\'selected\'" : "" %>><%-option.label%></option>',
|
||||||
|
' <% } %>',
|
||||||
|
' </select>',
|
||||||
|
'</div>'].join("\n")),
|
||||||
|
formatter: StringOrJSONFormatter,
|
||||||
initialize: function() {
|
initialize: function() {
|
||||||
/*
|
/*
|
||||||
* Initialization from the original control.
|
* Initialization from the original control.
|
||||||
@@ -47,14 +82,19 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
|
|||||||
this.field.get('url_with_id') || false, node_info
|
this.field.get('url_with_id') || false, node_info
|
||||||
]),
|
]),
|
||||||
cache_level = this.field.get('cache_level'),
|
cache_level = this.field.get('cache_level'),
|
||||||
/*
|
cache_node = this.field.get('cache_node');
|
||||||
* We needs to check, if we have already cached data for this url.
|
|
||||||
* If yes - use that, and do not bother about fetching it again,
|
cache_node = (cache_node && pgAdmin.Browser.Nodes['cache_node']) || node;
|
||||||
* and use it.
|
|
||||||
*/
|
/*
|
||||||
data = node.cache(url, node_info, cache_level);
|
* We needs to check, if we have already cached data for this url.
|
||||||
|
* If yes - use that, and do not bother about fetching it again,
|
||||||
|
* and use it.
|
||||||
|
*/
|
||||||
|
var data = cache_node.cache(url, node_info, cache_level);
|
||||||
|
|
||||||
if (_.isUndefined(data) || _.isNull(data)) {
|
if (_.isUndefined(data) || _.isNull(data)) {
|
||||||
m.trigger('pgadmin-view:fetching', m, self.field);
|
m.trigger('pgadmin:view:fetching', m, self.field);
|
||||||
$.ajax({
|
$.ajax({
|
||||||
async: false,
|
async: false,
|
||||||
url: full_url,
|
url: full_url,
|
||||||
@@ -63,13 +103,13 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
|
|||||||
* We will cache this data for short period of time for avoiding
|
* We will cache this data for short period of time for avoiding
|
||||||
* same calls.
|
* same calls.
|
||||||
*/
|
*/
|
||||||
data = node.cache(url, node_info, cache_level, res.data);
|
data = cache_node.cache(url, node_info, cache_level, res.data);
|
||||||
},
|
},
|
||||||
error: function() {
|
error: function() {
|
||||||
m.trigger('pgadmin-view:fetch:error', m, self.field);
|
m.trigger('pgadmin:view:fetch:error', m, self.field);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
m.trigger('pgadmin-view:fetched', m, self.field);
|
m.trigger('pgadmin:view:fetched', m, self.field);
|
||||||
}
|
}
|
||||||
// To fetch only options from cache, we do not need time from 'at'
|
// To fetch only options from cache, we do not need time from 'at'
|
||||||
// attribute but only options.
|
// attribute but only options.
|
||||||
@@ -90,28 +130,45 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
|
|||||||
self.field.set('options', data);
|
self.field.set('options', data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
render: function() {
|
||||||
|
/*
|
||||||
|
* Let SelectControl render it, we will do our magic on the
|
||||||
|
* select control in it.
|
||||||
|
*/
|
||||||
|
Backform.SelectControl.prototype.render.apply(this, arguments);
|
||||||
|
|
||||||
|
var d = this.field.toJSON(),
|
||||||
|
select2_opts = _.defaults({}, d.select2, this.defaults.select2);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add empty option as Select2 requires any empty '<option><option>' for
|
||||||
|
* some of its functionality to work and initialize select2 control.
|
||||||
|
*/
|
||||||
|
this.$el.find("select").select2(select2_opts);
|
||||||
|
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var formatNode = function(opt) {
|
||||||
|
if (!opt.id) {
|
||||||
|
return opt.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
var optimage = $(opt.element).data('image');
|
||||||
|
|
||||||
|
if(!optimage){
|
||||||
|
return opt.text;
|
||||||
|
} else {
|
||||||
|
return $(
|
||||||
|
'<span><span class="wcTabIcon ' + optimage + '"/>' + opt.text + '</span>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var NodeListByIdControl = Backform.NodeListByIdControl = NodeAjaxOptionsControl.extend({
|
var NodeListByIdControl = Backform.NodeListByIdControl = NodeAjaxOptionsControl.extend({
|
||||||
controlClassName: 'pgadmin-node-select form-control',
|
controlClassName: 'pgadmin-node-select form-control',
|
||||||
template: _.template([
|
|
||||||
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
|
|
||||||
'<div class="<%=Backform.controlsClassName%> <%=extraClasses.join(\' \')%>">',
|
|
||||||
' <select class="pgadmin-node-select form-control" name="<%=name%>" value="<%-value%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> >',
|
|
||||||
' <% if (first_empty) { %>',
|
|
||||||
' <option value="" <%="" === rawValue ? "selected=\'selected\'" : "" %>><%- empty_value %></option>',
|
|
||||||
' <% } %>',
|
|
||||||
' <% for (var i=0; i < options.length; i++) { %>',
|
|
||||||
' <% var option = options[i]; %>',
|
|
||||||
' <% if (!_.isUndefined(option.node)) { %>',
|
|
||||||
' <option value="<%-formatter.fromRaw(option.value)%>" <%=option.value === rawValue ? "selected=\'selected\'" : "" %> node="<%=option.node%>"><%-option.label%></option>',
|
|
||||||
' <% } else { %>',
|
|
||||||
' <option value="<%-formatter.fromRaw(option.value)%>" <%=option.value === rawValue ? "selected=\'selected\'" : "" %>><%-option.label%></option>',
|
|
||||||
' <% } %>',
|
|
||||||
' <% } %>',
|
|
||||||
' </select>',
|
|
||||||
'</div>'].join("\n")),
|
|
||||||
defaults: _.extend(NodeAjaxOptionsControl.prototype.defaults, {
|
defaults: _.extend(NodeAjaxOptionsControl.prototype.defaults, {
|
||||||
first_empty: true,
|
first_empty: true,
|
||||||
empty_value: '-- None --',
|
empty_value: '-- None --',
|
||||||
@@ -131,16 +188,27 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
|
|||||||
(node['node_label']).apply(node, [r, self.model, self]) :
|
(node['node_label']).apply(node, [r, self.model, self]) :
|
||||||
r.label),
|
r.label),
|
||||||
image= (_.isFunction(node['node_image']) ?
|
image= (_.isFunction(node['node_image']) ?
|
||||||
(node['node_image']).apply(node, [r, self.model, self]) : node.type);
|
(node['node_image']).apply(
|
||||||
|
node, [r, self.model, self]
|
||||||
|
) :
|
||||||
|
(node['node_image'] || ('icon-' + node.type)));
|
||||||
|
|
||||||
res.push({
|
res.push({
|
||||||
'value': r._id,
|
'value': r._id,
|
||||||
'node': image,
|
'image': image,
|
||||||
'label': l
|
'label': l
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
},
|
||||||
|
select2: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: 'Select from the list',
|
||||||
|
width: 'style',
|
||||||
|
templateResult: formatNode,
|
||||||
|
templateSelection: formatNode
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -161,11 +229,14 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
|
|||||||
var l = (_.isFunction(node['node_label']) ?
|
var l = (_.isFunction(node['node_label']) ?
|
||||||
(node['node_label']).apply(node, [r, self.model, self]) :
|
(node['node_label']).apply(node, [r, self.model, self]) :
|
||||||
r.label),
|
r.label),
|
||||||
image= (_.isFunction(node['node_image']) ?
|
image = (_.isFunction(node['node_image']) ?
|
||||||
(node['node_image']).apply(node, [r, self.model, self]) : node.type);
|
(node['node_image']).apply(
|
||||||
|
node, [r, self.model, self]
|
||||||
|
) :
|
||||||
|
(node['node_image'] || ('icon-' + node.type)));
|
||||||
res.push({
|
res.push({
|
||||||
'value': r.label,
|
'value': r.label,
|
||||||
'node': image,
|
'image': image,
|
||||||
'label': l
|
'label': l
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -176,5 +247,32 @@ function($, _, pgAdmin, Backbone, Backform, Alertify, Node) {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
return Backform.NodeListControl;
|
/*
|
||||||
|
* Global function to make visible particular dom element in it's parent
|
||||||
|
* with given class.
|
||||||
|
*/
|
||||||
|
$.fn.pgMakeVisible = function( cls ) {
|
||||||
|
return this.each(function() {
|
||||||
|
if (!this || !$(this.length))
|
||||||
|
return;
|
||||||
|
var top, p = $(this), hasScrollbar = function(j) {
|
||||||
|
if (j && j.length > 0) {
|
||||||
|
return j.get(0).scrollHeight > j.height();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
while(p) {
|
||||||
|
top = p.get(0).offsetTop + p.height();
|
||||||
|
p = p.parent();
|
||||||
|
if (hasScrollbar(p)) {
|
||||||
|
p.scrollTop(top);
|
||||||
|
}
|
||||||
|
if (p.hasClass(cls)) //'backform-tab'
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return Backform;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -582,13 +582,27 @@ table.backgrid tr.new {
|
|||||||
height: 0px; width: 0px;
|
height: 0px; width: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.width_percent_40 {
|
.width_percent_5 { width: 5%; }
|
||||||
width: 40%;
|
.width_percent_10 { width: 10%; }
|
||||||
}
|
.width_percent_15 { width: 15%; }
|
||||||
|
.width_percent_20 { width: 20%; }
|
||||||
.width_percent_60 {
|
.width_percent_25 { width: 25%; }
|
||||||
width: 60%;
|
.width_percent_30 { width: 30%; }
|
||||||
}
|
.width_percent_35 { width: 35%; }
|
||||||
|
.width_percent_40 { width: 40%; }
|
||||||
|
.width_percent_45 { width: 45%; }
|
||||||
|
.width_percent_50 { width: 50%; }
|
||||||
|
.width_percent_55 { width: 55%; }
|
||||||
|
.width_percent_60 { width: 60%; }
|
||||||
|
.width_percent_65 { width: 65%; }
|
||||||
|
.width_percent_70 { width: 70%; }
|
||||||
|
.width_percent_75 { width: 75%; }
|
||||||
|
.width_percent_80 { width: 80%; }
|
||||||
|
.width_percent_85 { width: 85%; }
|
||||||
|
.width_percent_90 { width: 90%; }
|
||||||
|
.width_percent_95 { width: 95%; }
|
||||||
|
.width_percent_99 { width: 99%; }
|
||||||
|
.width_percent_100 { width: 100%; }
|
||||||
|
|
||||||
.pg-prop-status-bar {
|
.pg-prop-status-bar {
|
||||||
left: 0px;
|
left: 0px;
|
||||||
@@ -611,3 +625,27 @@ table.backgrid tr.new {
|
|||||||
right: 0px;
|
right: 0px;
|
||||||
bottom :0;
|
bottom :0;
|
||||||
}
|
}
|
||||||
|
.subnode-header-form {
|
||||||
|
background-color:#2c76b4;
|
||||||
|
color:#FFFFFF;
|
||||||
|
padding:3px 0 10px 0;
|
||||||
|
border-top: solid 1.5px white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subnode-header-form button.add {
|
||||||
|
float:right;
|
||||||
|
margin-right:15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-search--inline .select2-search__field {
|
||||||
|
background: transparent none repeat scroll 0% 0%;
|
||||||
|
border: medium none;
|
||||||
|
outline: 0px none;
|
||||||
|
box-shadow: none;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.rolmembership {
|
||||||
|
border: 1.5px solid #faebd7;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -74,7 +74,8 @@
|
|||||||
'multiline': ['textarea', 'textarea', 'string'],
|
'multiline': ['textarea', 'textarea', 'string'],
|
||||||
'collection': ['sub-node-collection', 'sub-node-collection', 'string'],
|
'collection': ['sub-node-collection', 'sub-node-collection', 'string'],
|
||||||
'uniqueColCollection': ['unique-col-collection', 'unique-col-collection', 'string'],
|
'uniqueColCollection': ['unique-col-collection', 'unique-col-collection', 'string'],
|
||||||
'switch' : 'switch'
|
'switch' : 'switch',
|
||||||
|
'select2': 'select2',
|
||||||
};
|
};
|
||||||
|
|
||||||
var getMappedControl = Backform.getMappedControl = function(type, mode) {
|
var getMappedControl = Backform.getMappedControl = function(type, mode) {
|
||||||
@@ -111,95 +112,131 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var BackformControlInit = Backform.Control.prototype.initialize,
|
||||||
|
BackformControlRemove = Backform.Control.prototype.remove;
|
||||||
|
|
||||||
// Override the Backform.Control to allow to track changes in dependencies,
|
// Override the Backform.Control to allow to track changes in dependencies,
|
||||||
// and rerender the View element
|
// and rerender the View element
|
||||||
var BackformControlInit = Backform.Control.prototype.initialize;
|
_.extend(Backform.Control.prototype, {
|
||||||
Backform.Control.prototype.initialize = function() {
|
|
||||||
BackformControlInit.apply(this, arguments);
|
|
||||||
|
|
||||||
// Listen to the dependent fields in the model for any change
|
initialize: function() {
|
||||||
var deps = this.field.get('deps');
|
BackformControlInit.apply(this, arguments);
|
||||||
var that = this;
|
|
||||||
if (deps && _.isArray(deps))
|
|
||||||
_.each(deps, function(d) {
|
|
||||||
attrArr = d.split('.');
|
|
||||||
name = attrArr.shift();
|
|
||||||
that.listenTo(that.model, "change:" + name, that.render);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
Backform.Control.prototype.template = _.template([
|
|
||||||
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
|
|
||||||
'<div class="<%=Backform.controlsClassName%>">',
|
|
||||||
' <span class="<%=Backform.controlClassName%> uneditable-input" <%=disabled ? "disabled" : ""%>>',
|
|
||||||
' <%=value%>',
|
|
||||||
' </span>',
|
|
||||||
'</div>'
|
|
||||||
].join("\n"));
|
|
||||||
Backform.Control.prototype.clearInvalid = function() {
|
|
||||||
this.$el.removeClass(Backform.errorClassName);
|
|
||||||
this.$el.find(".pgadmin-control-error-message").remove();
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
Backform.Control.prototype.updateInvalid = function() {
|
|
||||||
var self = this;
|
|
||||||
var errorModel = this.model.errorModel;
|
|
||||||
if (!(errorModel instanceof Backbone.Model)) return this;
|
|
||||||
|
|
||||||
this.clearInvalid();
|
// Listen to the dependent fields in the model for any change
|
||||||
|
var deps = this.field.get('deps');
|
||||||
|
var self = this;
|
||||||
|
|
||||||
this.$el.find(':input').not('button').each(function(ix, el) {
|
if (deps && _.isArray(deps)) {
|
||||||
var attrArr = $(el).attr('name').split('.'),
|
_.each(deps, function(d) {
|
||||||
name = attrArr.shift(),
|
attrArr = d.split('.');
|
||||||
path = attrArr.join('.'),
|
name = attrArr.shift();
|
||||||
error = self.keyPathAccessor(errorModel.toJSON(), $(el).attr('name'));
|
self.listenTo(self.model, "change:" + name, self.render);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
remove: function() {
|
||||||
|
// Listen to the dependent fields in the model for any change
|
||||||
|
var self = this,
|
||||||
|
deps = self.field.get('deps');
|
||||||
|
|
||||||
|
self.stopListening(self.model, "change:" + name, self.render);
|
||||||
|
self.stopListening(self.model.errorModel, "change:" + name, self.updateInvalid);
|
||||||
|
|
||||||
|
if (deps && _.isArray(deps)) {
|
||||||
|
_.each(deps, function(d) {
|
||||||
|
|
||||||
|
attrArr = d.split('.');
|
||||||
|
name = attrArr.shift();
|
||||||
|
|
||||||
|
self.stopListening(that.model, "change:" + name, self.render);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BackformControlRemove) {
|
||||||
|
BackformControlRemove.apply(self, arguments);
|
||||||
|
} else {
|
||||||
|
Backbone.View.prototype.remove.apply(self, arguments);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
template: _.template([
|
||||||
|
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
|
||||||
|
'<div class="<%=Backform.controlsClassName%>">',
|
||||||
|
' <span class="<%=Backform.controlClassName%> uneditable-input" <%=disabled ? "disabled" : ""%>>',
|
||||||
|
' <%=value%>',
|
||||||
|
' </span>',
|
||||||
|
'</div>'
|
||||||
|
].join("\n")),
|
||||||
|
|
||||||
|
clearInvalid: function() {
|
||||||
|
this.$el.removeClass(Backform.errorClassName);
|
||||||
|
this.$el.find(".pgadmin-control-error-message").remove();
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateInvalid: function() {
|
||||||
|
var self = this,
|
||||||
|
errorModel = this.model.errorModel;
|
||||||
|
|
||||||
|
if (!(errorModel instanceof Backbone.Model)) return this;
|
||||||
|
|
||||||
|
this.clearInvalid();
|
||||||
|
|
||||||
|
this.$el.find(':input').not('button').each(function(ix, el) {
|
||||||
|
var attrArr = $(el).attr('name').split('.'),
|
||||||
|
name = attrArr.shift(),
|
||||||
|
path = attrArr.join('.'),
|
||||||
|
error = self.keyPathAccessor(errorModel.toJSON(), $(el).attr('name'));
|
||||||
|
|
||||||
if (_.isEmpty(error)) return;
|
if (_.isEmpty(error)) return;
|
||||||
|
|
||||||
self.$el.addClass(Backform.errorClassName).append(
|
self.$el.addClass(Backform.errorClassName).append(
|
||||||
$("<div></div>").addClass('pgadmin-control-error-message col-xs-12 help-block').text(error)
|
$("<div></div>").addClass('pgadmin-control-error-message col-xs-12 help-block').text(error)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Overriding the render function of the control to allow us to eval the
|
* Overriding the render function of the control to allow us to eval the
|
||||||
* values properly.
|
* values properly.
|
||||||
*/
|
*/
|
||||||
Backform.Control.prototype.render = function() {
|
render: function() {
|
||||||
var field = _.defaults(this.field.toJSON(), this.defaults),
|
var field = _.defaults(this.field.toJSON(), this.defaults),
|
||||||
attributes = this.model.toJSON(),
|
attributes = this.model.toJSON(),
|
||||||
attrArr = field.name.split('.'),
|
attrArr = field.name.split('.'),
|
||||||
name = attrArr.shift(),
|
name = attrArr.shift(),
|
||||||
path = attrArr.join('.'),
|
path = attrArr.join('.'),
|
||||||
rawValue = this.keyPathAccessor(attributes[name], path),
|
rawValue = this.keyPathAccessor(attributes[name], path),
|
||||||
data = _.extend(field, {
|
data = _.extend(field, {
|
||||||
rawValue: rawValue,
|
rawValue: rawValue,
|
||||||
value: this.formatter.fromRaw(rawValue, this.model),
|
value: this.formatter.fromRaw(rawValue, this.model),
|
||||||
attributes: attributes,
|
attributes: attributes,
|
||||||
formatter: this.formatter
|
formatter: this.formatter
|
||||||
}),
|
}),
|
||||||
evalF = function(f, d, m) {
|
evalF = function(f, d, m) {
|
||||||
return (_.isFunction(f) ? !!f.apply(d, [m]) : !!f);
|
return (_.isFunction(f) ? !!f.apply(d, [m]) : !!f);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Evaluate the disabled, visible, and required option
|
// Evaluate the disabled, visible, and required option
|
||||||
_.extend(data, {
|
_.extend(data, {
|
||||||
disabled: evalF(data.disabled, data, this.model),
|
disabled: evalF(data.disabled, data, this.model),
|
||||||
visible: evalF(data.visible, data, this.model),
|
visible: evalF(data.visible, data, this.model),
|
||||||
required: evalF(data.required, data, this.model)
|
required: evalF(data.required, data, this.model)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clean up first
|
// Clean up first
|
||||||
this.$el.removeClass(Backform.hiddenClassname);
|
this.$el.removeClass(Backform.hiddenClassname);
|
||||||
|
|
||||||
if (!data.visible)
|
if (!data.visible)
|
||||||
this.$el.addClass(Backform.hiddenClassname);
|
this.$el.addClass(Backform.hiddenClassname);
|
||||||
|
|
||||||
this.$el.html(this.template(data)).addClass(field.name);
|
this.$el.html(this.template(data)).addClass(field.name);
|
||||||
this.updateInvalid();
|
this.updateInvalid();
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
};
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Overriding the render function of the select control to allow us to use
|
* Overriding the render function of the select control to allow us to use
|
||||||
@@ -235,7 +272,7 @@
|
|||||||
data.options = data.options.apply(this)
|
data.options = data.options.apply(this)
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
data = []
|
data.options = []
|
||||||
this.model.trigger('pgadmin-view:transform:error', m, self.field, e);
|
this.model.trigger('pgadmin-view:transform:error', m, self.field, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -582,70 +619,113 @@
|
|||||||
var uniqueCol = this.field.get('uniqueCol') || [],
|
var uniqueCol = this.field.get('uniqueCol') || [],
|
||||||
m = this.field.get('model'),
|
m = this.field.get('model'),
|
||||||
schema = m.prototype.schema || m.__super__.schema,
|
schema = m.prototype.schema || m.__super__.schema,
|
||||||
columns = [];
|
columns = [],
|
||||||
|
self = this;
|
||||||
|
|
||||||
_.each(schema, function(s) {
|
_.each(schema, function(s) {
|
||||||
columns.push(s.id);
|
columns.push(s.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if unique columns provided are also in model attributes.
|
// Check if unique columns provided are also in model attributes.
|
||||||
if (uniqueCol.length > _.intersection(columns, uniqueCol).length){
|
if (uniqueCol.length > _.intersection(columns, uniqueCol).length) {
|
||||||
errorMsg = "Developer: Unique column/s [ "+_.difference(uniqueCol, columns)+" ] not found in collection model [ " + columns +" ]."
|
errorMsg = "Developer: Unique column/s [ "+_.difference(uniqueCol, columns)+" ] not found in collection model [ " + columns +" ]."
|
||||||
alert (errorMsg);
|
alert (errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
var collection = this.model.get(this.field.get('name')),
|
var collection = self.collection = self.model.get(self.field.get('name'));
|
||||||
self = this;
|
|
||||||
if (!collection) {
|
if (!collection) {
|
||||||
collection = new (pgAdmin.Browser.Node.Collection)(null, {
|
collection = self.collection = new (pgAdmin.Browser.Node.Collection)(
|
||||||
model: self.field.get('model'),
|
null,
|
||||||
silent: true,
|
{
|
||||||
handler: self.model.handler || self.model
|
model: self.field.get('model'),
|
||||||
});
|
silent: true,
|
||||||
|
handler: self.model.handler || self.model
|
||||||
|
});
|
||||||
self.model.set(self.field.get('name'), collection, {silent: true});
|
self.model.set(self.field.get('name'), collection, {silent: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.listenTo(collection, "add", self.collectionChanged);
|
self.listenTo(collection, "add", self.collectionChanged);
|
||||||
self.listenTo(collection, "change", self.collectionChanged);
|
self.listenTo(collection, "change", self.collectionChanged);
|
||||||
},
|
},
|
||||||
collectionChanged: function(newModel, coll, op) {
|
remove: function() {
|
||||||
var uniqueCol = this.field.get('uniqueCol') || [],
|
var self = this;
|
||||||
uniqueChangedAttr = [],
|
|
||||||
changedAttr = newModel.changedAttributes();
|
|
||||||
// Check if changed model attributes are also in unique columns. And then only check for uniqueness.
|
|
||||||
if (changedAttr) {
|
|
||||||
_.each(uniqueCol, function(col) {
|
|
||||||
if ( _.has(changedAttr,col))
|
|
||||||
{
|
|
||||||
uniqueChangedAttr.push(col);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if(uniqueChangedAttr.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var collection = this.model.get(this.field.get('name'));
|
self.stopListening(collection, "add", self.collectionChanged);
|
||||||
this.stopListening(collection, "change", this.collectionChanged);
|
self.stopListening(collection, "change", self.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 it's previous values.
|
Backform.Control.prototype.remove.apply(this, arguments);
|
||||||
collection.each(function(model) {
|
},
|
||||||
if (newModel != model) {
|
collectionChanged: function(newModel, coll, op) {
|
||||||
var duplicateAttrValues = []
|
var uniqueCol = this.field.get('uniqueCol') || [],
|
||||||
_.each(uniqueCol, function(attr) {
|
uniqueChangedAttr = [],
|
||||||
attrValue = newModel.get(attr);
|
self = this;
|
||||||
if (!_.isUndefined(attrValue) && attrValue == model.get(attr)) {
|
// Check if changed model attributes are also in unique columns. And then only check for uniqueness.
|
||||||
duplicateAttrValues.push(attrValue)
|
if (newModel.attributes) {
|
||||||
}
|
_.each(uniqueCol, function(col) {
|
||||||
});
|
if (_.has(newModel.attributes,col))
|
||||||
if (duplicateAttrValues.length == uniqueCol.length){
|
{
|
||||||
newModel.set(uniqueChangedAttr[0], newModel.previous(uniqueChangedAttr[0]), {silent: true});
|
uniqueChangedAttr.push(col);
|
||||||
// TODO- Need to add notification in status bar for unique column.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.listenTo(collection, "change", this.collectionChanged);
|
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) {
|
||||||
|
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]), {silent: true});
|
||||||
|
}
|
||||||
|
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() {
|
render: function() {
|
||||||
var field = _.defaults(this.field.toJSON(), this.defaults),
|
var field = _.defaults(this.field.toJSON(), this.defaults),
|
||||||
@@ -673,7 +753,7 @@
|
|||||||
canDelete: evalF(data.canDelete, this.model)
|
canDelete: evalF(data.canDelete, this.model)
|
||||||
});
|
});
|
||||||
// Show Backgrid Control
|
// Show Backgrid Control
|
||||||
grid = (data.subnode == undefined) ? "" : this.showGridControl(data);
|
grid = this.showGridControl(data);
|
||||||
|
|
||||||
this.$el.html(grid).addClass(field.name);
|
this.$el.html(grid).addClass(field.name);
|
||||||
this.updateInvalid();
|
this.updateInvalid();
|
||||||
@@ -687,6 +767,10 @@
|
|||||||
"</div>"].join("\n"),
|
"</div>"].join("\n"),
|
||||||
gridBody = $("<div class='pgadmin-control-group backgrid form-group col-xs-12 object subnode'></div>").append(gridHeader);
|
gridBody = $("<div class='pgadmin-control-group backgrid form-group col-xs-12 object subnode'></div>").append(gridHeader);
|
||||||
|
|
||||||
|
if (!(data.subnode)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
var subnode = data.subnode.schema ? data.subnode : data.subnode.prototype,
|
var subnode = data.subnode.schema ? data.subnode : data.subnode.prototype,
|
||||||
gridSchema = Backform.generateGridColumnsFromModel(
|
gridSchema = Backform.generateGridColumnsFromModel(
|
||||||
data.node_info, subnode, this.field.get('mode'), data.columns
|
data.node_info, subnode, this.field.get('mode'), data.columns
|
||||||
@@ -709,10 +793,10 @@
|
|||||||
|
|
||||||
var collection = this.model.get(data.name);
|
var collection = this.model.get(data.name);
|
||||||
// Initialize a new Grid instance
|
// Initialize a new Grid instance
|
||||||
var grid = new Backgrid.Grid({
|
var grid = self.grid = new Backgrid.Grid({
|
||||||
columns: gridSchema.columns,
|
columns: gridSchema.columns,
|
||||||
collection: collection,
|
collection: collection,
|
||||||
className: "backgrid table-bordered"
|
className: "backgrid table-bordered"
|
||||||
});
|
});
|
||||||
|
|
||||||
// Render subNode grid
|
// Render subNode grid
|
||||||
@@ -728,35 +812,38 @@
|
|||||||
// Add button callback
|
// Add button callback
|
||||||
if (!(data.disabled || data.canAdd == false)) {
|
if (!(data.disabled || data.canAdd == false)) {
|
||||||
$dialog.find('button.add').first().click(function(e) {
|
$dialog.find('button.add').first().click(function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var allowMultipleEmptyRows = !!self.field.get('allowMultipleEmptyRows');
|
var allowMultipleEmptyRows = !!self.field.get('allowMultipleEmptyRows');
|
||||||
|
|
||||||
// If allowMultipleEmptyRows is not set or is false then don't allow second new empty row.
|
// If allowMultipleEmptyRows is not set or is false then don't allow second new empty row.
|
||||||
// There should be only one empty row.
|
// There should be only one empty row.
|
||||||
if (!allowMultipleEmptyRows && collection){
|
if (!allowMultipleEmptyRows && collection) {
|
||||||
var isEmpty = false;
|
var isEmpty = false;
|
||||||
collection.each(function(model){
|
collection.each(function(model) {
|
||||||
var modelValues = [];
|
var modelValues = [];
|
||||||
_.each(model.attributes, function(val, key){
|
_.each(model.attributes, function(val, key) {
|
||||||
modelValues.push(val);
|
modelValues.push(val);
|
||||||
})
|
})
|
||||||
if(!_.some(modelValues, _.identity)){
|
if(!_.some(modelValues, _.identity)) {
|
||||||
isEmpty = true;
|
isEmpty = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if(isEmpty){
|
if(isEmpty) {
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$(grid.body.$el.find($("tr.new"))).removeClass("new")
|
$(grid.body.$el.find($("tr.new"))).removeClass("new")
|
||||||
var m = new (data.model)(null, {silent: true});
|
var m = new (data.model)(null, {silent: true});
|
||||||
collection.add(m);
|
collection.add(m);
|
||||||
|
|
||||||
var idx = collection.indexOf(m);
|
var idx = collection.indexOf(m),
|
||||||
newRow = grid.body.rows[idx].$el;
|
newRow = grid.body.rows[idx].$el;
|
||||||
newRow.addClass("new");
|
|
||||||
return false;
|
newRow.addClass("new");
|
||||||
|
$(newRow).pgMakeVisible('backform-tab');
|
||||||
|
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -869,7 +956,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize a new Grid instance
|
// Initialize a new Grid instance
|
||||||
var grid = new Backgrid.Grid({
|
var grid = self.grid = new Backgrid.Grid({
|
||||||
columns: gridSchema.columns,
|
columns: gridSchema.columns,
|
||||||
collection: collection,
|
collection: collection,
|
||||||
className: "backgrid table-bordered"
|
className: "backgrid table-bordered"
|
||||||
@@ -890,10 +977,11 @@
|
|||||||
$dialog.find('button.add').click(function(e) {
|
$dialog.find('button.add').click(function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
grid.insertRow({});
|
grid.insertRow({});
|
||||||
newRow = $(grid.body.rows[collection.length - 1].$el);
|
var newRow = $(grid.body.rows[collection.length - 1].$el);
|
||||||
newRow.attr("class", "new").click(function(e) {
|
newRow.attr("class", "new").click(function(e) {
|
||||||
$(this).attr("class", "");
|
$(this).attr("class", "");
|
||||||
});
|
});
|
||||||
|
$(newRow).pgMakeVisible('backform-tab');
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1152,11 +1240,13 @@
|
|||||||
schema_node: schema_node,
|
schema_node: schema_node,
|
||||||
visible: (mode == 'properties'?
|
visible: (mode == 'properties'?
|
||||||
(ver_in_limit ?
|
(ver_in_limit ?
|
||||||
(s.version || true) : false) : s.version || true)
|
(s.version || true) : false) : s.version || true),
|
||||||
|
node: node,
|
||||||
|
node_data: treeData
|
||||||
});
|
});
|
||||||
delete o.id;
|
delete o.id;
|
||||||
|
|
||||||
// Temporarily store in dictionaly format for
|
// Temporarily store in dictionary format for
|
||||||
// utilizing it later.
|
// utilizing it later.
|
||||||
groups[group].push(o);
|
groups[group].push(o);
|
||||||
}
|
}
|
||||||
@@ -1182,7 +1272,7 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
return groups;
|
return groups;
|
||||||
}
|
};
|
||||||
|
|
||||||
return Backform;
|
return Backform;
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -352,7 +352,7 @@
|
|||||||
_.defaults(
|
_.defaults(
|
||||||
{'disabled': !editable},
|
{'disabled': !editable},
|
||||||
col.select2,
|
col.select2,
|
||||||
this.defatuls.select2
|
this.defaults.select2
|
||||||
));
|
));
|
||||||
|
|
||||||
this.delegateEvents();
|
this.delegateEvents();
|
||||||
|
|||||||
Reference in New Issue
Block a user