2015-06-30 05:51:55 +00:00
|
|
|
(function(root, factory) {
|
|
|
|
|
|
|
|
// Set up Backform appropriately for the environment. Start with AMD.
|
|
|
|
if (typeof define === 'function' && define.amd) {
|
2016-01-04 17:53:16 +00:00
|
|
|
define([
|
2016-01-05 11:19:58 +00:00
|
|
|
'underscore', 'underscore.string', 'jquery', 'backbone', 'backform',
|
2016-04-21 19:13:00 +00:00
|
|
|
'backgrid', 'codemirror', 'pgadmin.backgrid', 'codemirror/mode/sql/sql',
|
2016-05-06 12:25:50 +00:00
|
|
|
'select2', 'pgadmin.browser.messages'
|
2016-01-04 17:53:16 +00:00
|
|
|
],
|
2016-01-05 11:19:58 +00:00
|
|
|
function(_, S, $, Backbone, Backform, Backgrid, CodeMirror) {
|
2015-06-30 05:51:55 +00:00
|
|
|
// Export global even in AMD case in case this script is loaded with
|
|
|
|
// others that may still expect a global Backform.
|
2016-01-05 11:19:58 +00:00
|
|
|
return factory(root, _, S, $, Backbone, Backform, Backgrid, CodeMirror);
|
2015-06-30 05:51:55 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// 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,
|
2015-10-28 17:06:09 +00:00
|
|
|
Backform = require('backform') || root.Backform,
|
|
|
|
Backgrid = require('backgrid') || root.Backgrid;
|
2015-12-26 10:45:12 +00:00
|
|
|
CodeMirror = require('codemirror') || root.CodeMirror;
|
2015-10-28 17:06:09 +00:00
|
|
|
pgAdminBackgrid = require('pgadmin.backgrid');
|
2016-01-05 11:19:58 +00:00
|
|
|
S = require('underscore.string');
|
|
|
|
factory(root, _, S, $, Backbone, Backform, Backgrid, CodeMirror);
|
2015-06-30 05:51:55 +00:00
|
|
|
|
|
|
|
// Finally, as a browser global.
|
|
|
|
} else {
|
2016-01-05 11:19:58 +00:00
|
|
|
factory(root, root._, root.s, (root.jQuery || root.Zepto || root.ender || root.$), root.Backbone, root.Backform, root.Backgrid, root.CodeMirror);
|
2015-06-30 05:51:55 +00:00
|
|
|
}
|
2016-01-05 11:19:58 +00:00
|
|
|
}(this, function(root, _, S, $, Backbone, Backform, Backgrid, CodeMirror) {
|
2015-06-30 05:51:55 +00:00
|
|
|
|
2015-11-03 07:06:32 +00:00
|
|
|
var pgAdmin = (window.pgAdmin = window.pgAdmin || {});
|
|
|
|
|
2015-12-16 08:13:04 +00:00
|
|
|
pgAdmin.editableCell = function() {
|
Resolved few intialization issue with Node model data, moved the
privileges functionality out of the backform.pgadmin.js to make it more
modular. Now - privileges will expect the privileges data in following
format:
<name_of_the_property> : [{
"privileges": [{
"privilege_type": <privilege_type>,
"privilege": true,
"with_grant": false
},
...
],
"grantee": <grantee>,
"grantor": <grantor>
},
...
]
Example:
acl": [{
"privileges": [{
"privilege_type": "CONNECT",
"privilege": true,
"with_grant": false
}],
"grantee": '',
"grantor": 'ashesh'
},{
"privileges": [{
"privilege_type": "CREATE",
"privilege": true,
"with_grant": false
},{
"privilege": true,
"privilege_type": "TEMPORARY",
"with_grant": false
}],
"grantee": test,
"grantor": ashesh
}]
2015-12-23 06:40:20 +00:00
|
|
|
if (this.attributes && !_.isUndefined(this.attributes.disabled) &&
|
|
|
|
!_.isNull(this.attributes.disabled)) {
|
2015-11-03 07:06:32 +00:00
|
|
|
if(_.isFunction(this.attributes.disabled)) {
|
2015-12-17 13:00:39 +00:00
|
|
|
return !(this.attributes.disabled.apply(this, arguments));
|
2015-11-03 07:06:32 +00:00
|
|
|
}
|
|
|
|
if (_.isBoolean(this.attributes.disabled)) {
|
|
|
|
return !this.attributes.disabled;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-06-30 05:51:55 +00:00
|
|
|
// HTML markup global class names. More can be added by individual controls
|
|
|
|
// using _.extend. Look at RadioControl as an example.
|
|
|
|
_.extend(Backform, {
|
|
|
|
controlLabelClassName: "control-label col-sm-4",
|
|
|
|
controlsClassName: "pgadmin-controls col-sm-8",
|
|
|
|
groupClassName: "pgadmin-control-group form-group col-xs-12",
|
|
|
|
setGroupClassName: "set-group col-xs-12",
|
|
|
|
tabClassName: "backform-tab col-xs-12",
|
|
|
|
setGroupContentClassName: "fieldset-content col-xs-12"
|
|
|
|
});
|
|
|
|
|
2015-10-28 17:06:09 +00:00
|
|
|
var controlMapper = Backform.controlMapper = {
|
2016-01-05 11:19:58 +00:00
|
|
|
'int': ['uneditable-input', 'integer', 'integer'],
|
2015-10-28 17:06:09 +00:00
|
|
|
'text': ['uneditable-input', 'input', 'string'],
|
2016-02-22 11:48:02 +00:00
|
|
|
'numeric': ['uneditable-input', 'numeric', 'numeric'],
|
2015-10-28 17:06:09 +00:00
|
|
|
'date': 'datepicker',
|
|
|
|
'boolean': 'boolean',
|
|
|
|
'options': ['readonly-option', 'select', Backgrid.Extension.PGSelectCell],
|
|
|
|
'multiline': ['textarea', 'textarea', 'string'],
|
2015-12-04 10:19:08 +00:00
|
|
|
'collection': ['sub-node-collection', 'sub-node-collection', 'string'],
|
2015-12-17 13:00:39 +00:00
|
|
|
'uniqueColCollection': ['unique-col-collection', 'unique-col-collection', 'string'],
|
2016-01-09 12:29:56 +00:00
|
|
|
'switch' : 'switch',
|
2016-04-28 06:22:04 +00:00
|
|
|
'select2': 'select2',
|
|
|
|
'note': 'note'
|
2015-10-28 17:06:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
var getMappedControl = Backform.getMappedControl = function(type, mode) {
|
|
|
|
if (type in Backform.controlMapper) {
|
|
|
|
var m = Backform.controlMapper[type];
|
|
|
|
|
|
|
|
if (!_.isArray(m)) {
|
|
|
|
return m;
|
|
|
|
}
|
|
|
|
|
|
|
|
var idx = 1, len = _.size(m);
|
|
|
|
|
|
|
|
switch (mode) {
|
|
|
|
case 'properties':
|
|
|
|
idx = 0;
|
|
|
|
break;
|
|
|
|
case 'edit':
|
|
|
|
case 'create':
|
|
|
|
case 'control':
|
|
|
|
idx = 1;
|
|
|
|
break;
|
|
|
|
case 'cell':
|
|
|
|
idx = 2;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
idx = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return m[idx > len ? 0 : idx];
|
|
|
|
}
|
2016-01-17 16:51:02 +00:00
|
|
|
return type;
|
2015-10-28 17:06:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-01-09 12:29:56 +00:00
|
|
|
var BackformControlInit = Backform.Control.prototype.initialize,
|
|
|
|
BackformControlRemove = Backform.Control.prototype.remove;
|
|
|
|
|
2015-07-14 03:59:44 +00:00
|
|
|
// Override the Backform.Control to allow to track changes in dependencies,
|
|
|
|
// and rerender the View element
|
2016-01-09 12:29:56 +00:00
|
|
|
_.extend(Backform.Control.prototype, {
|
2015-12-17 13:00:39 +00:00
|
|
|
|
2016-03-24 07:23:11 +00:00
|
|
|
defaults: _.extend(Backform.Control.prototype.defaults, {helpMessage: null}),
|
|
|
|
|
2016-01-09 12:29:56 +00:00
|
|
|
initialize: function() {
|
|
|
|
BackformControlInit.apply(this, arguments);
|
2015-12-17 13:00:39 +00:00
|
|
|
|
2016-01-09 12:29:56 +00:00
|
|
|
// Listen to the dependent fields in the model for any change
|
|
|
|
var deps = this.field.get('deps');
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
if (deps && _.isArray(deps)) {
|
|
|
|
_.each(deps, function(d) {
|
|
|
|
attrArr = d.split('.');
|
|
|
|
name = attrArr.shift();
|
|
|
|
self.listenTo(self.model, "change:" + name, self.render);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
remove: function() {
|
2016-03-07 10:35:01 +00:00
|
|
|
// Remove the events for the dependent fields in the model
|
2016-01-09 12:29:56 +00:00
|
|
|
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();
|
|
|
|
|
2016-01-15 11:17:17 +00:00
|
|
|
self.stopListening(self.model, "change:" + name, self.render);
|
2016-01-09 12:29:56 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-03-07 10:35:01 +00:00
|
|
|
if (this.cleanup) {
|
|
|
|
this.cleanup.apply(this);
|
|
|
|
}
|
|
|
|
|
2016-01-09 12:29:56 +00:00
|
|
|
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>',
|
2016-03-24 07:23:11 +00:00
|
|
|
'</div>',
|
|
|
|
'<% if (helpMessage && helpMessage.length) { %>',
|
|
|
|
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
|
|
|
|
'<% } %>',
|
2016-01-09 12:29:56 +00:00
|
|
|
].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();
|
|
|
|
|
2016-02-05 08:06:57 +00:00
|
|
|
/*
|
|
|
|
* Find input which have name attribute.
|
|
|
|
*/
|
|
|
|
this.$el.find(':input[name]').not('button').each(function(ix, el) {
|
2016-01-09 12:29:56 +00:00
|
|
|
var attrArr = $(el).attr('name').split('.'),
|
|
|
|
name = attrArr.shift(),
|
|
|
|
path = attrArr.join('.'),
|
|
|
|
error = self.keyPathAccessor(errorModel.toJSON(), $(el).attr('name'));
|
2015-12-17 13:00:39 +00:00
|
|
|
|
|
|
|
if (_.isEmpty(error)) return;
|
|
|
|
|
|
|
|
self.$el.addClass(Backform.errorClassName).append(
|
2016-03-09 16:03:24 +00:00
|
|
|
$("<div></div>").addClass('pgadmin-control-error-message col-xs-offset-4 col-xs-8 help-block').text(error)
|
2016-01-09 12:29:56 +00:00
|
|
|
);
|
|
|
|
});
|
|
|
|
},
|
2015-12-17 13:00:39 +00:00
|
|
|
|
2016-01-09 12:29:56 +00:00
|
|
|
/*
|
|
|
|
* Overriding the render function of the control to allow us to eval the
|
|
|
|
* values properly.
|
|
|
|
*/
|
|
|
|
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);
|
|
|
|
};
|
2016-01-04 13:57:51 +00:00
|
|
|
|
2016-01-09 12:29:56 +00:00
|
|
|
// Evaluate the disabled, visible, and required option
|
|
|
|
_.extend(data, {
|
|
|
|
disabled: evalF(data.disabled, data, this.model),
|
|
|
|
visible: evalF(data.visible, data, this.model),
|
|
|
|
required: evalF(data.required, data, this.model)
|
|
|
|
});
|
2016-01-04 13:57:51 +00:00
|
|
|
|
2016-01-09 12:29:56 +00:00
|
|
|
// Clean up first
|
|
|
|
this.$el.removeClass(Backform.hiddenClassname);
|
2016-01-04 13:57:51 +00:00
|
|
|
|
2016-01-09 12:29:56 +00:00
|
|
|
if (!data.visible)
|
|
|
|
this.$el.addClass(Backform.hiddenClassname);
|
2016-01-04 13:57:51 +00:00
|
|
|
|
2016-01-09 12:29:56 +00:00
|
|
|
this.$el.html(this.template(data)).addClass(field.name);
|
|
|
|
this.updateInvalid();
|
2016-01-04 13:57:51 +00:00
|
|
|
|
2016-01-09 12:29:56 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
});
|
2016-01-04 12:22:30 +00:00
|
|
|
|
2016-01-12 08:10:32 +00:00
|
|
|
/*
|
|
|
|
* Override the input control events in order to reslove the issue related to
|
|
|
|
* not updating the value sometimes in the input control.
|
|
|
|
*/
|
2016-03-15 13:28:16 +00:00
|
|
|
_.extend(
|
|
|
|
Backform.InputControl.prototype, {
|
|
|
|
events: {
|
|
|
|
"change input": "onChange",
|
|
|
|
"blur input": "onChange",
|
|
|
|
"keyup input": "onKeyUp",
|
|
|
|
"focus input": "clearInvalid"
|
|
|
|
},
|
|
|
|
onKeyUp: function(ev) {
|
|
|
|
if (this.key_timeout) {
|
|
|
|
clearTimeout(this.key_timeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.keyup_timeout = setTimeout(function() {
|
|
|
|
this.onChange(ev);
|
|
|
|
}.bind(this), 400);
|
|
|
|
}
|
|
|
|
});
|
2016-01-12 08:10:32 +00:00
|
|
|
|
2016-02-05 08:06:57 +00:00
|
|
|
/*
|
|
|
|
* Override the textarea control events in order to resolve the issue related
|
|
|
|
* to not updating the value in model on certain browsers in few situations
|
|
|
|
* like copy/paste, deletion using backspace.
|
|
|
|
*
|
|
|
|
* Reference:
|
|
|
|
* http://stackoverflow.com/questions/11338592/how-can-i-bind-to-the-change-event-of-a-textarea-in-jquery
|
|
|
|
*/
|
2016-03-10 15:31:43 +00:00
|
|
|
_.extend(
|
2016-03-15 13:28:16 +00:00
|
|
|
Backform.TextareaControl.prototype, {
|
|
|
|
defaults: _.extend(
|
2016-03-24 07:23:11 +00:00
|
|
|
Backform.TextareaControl.prototype.defaults, {rows: 5, helpMessage: null}
|
2016-03-15 13:28:16 +00:00
|
|
|
),
|
|
|
|
events : {
|
|
|
|
"change textarea": "onChange",
|
|
|
|
"keyup textarea": "onKeyUp",
|
|
|
|
"paste textarea": "onChange",
|
|
|
|
"selectionchange textarea": "onChange",
|
|
|
|
"focus textarea": "clearInvalid"
|
|
|
|
},
|
|
|
|
template: _.template([
|
|
|
|
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
|
|
|
|
'<div class="<%=Backform.controlsClassName%>">',
|
|
|
|
' <textarea ',
|
|
|
|
' class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>"',
|
|
|
|
' maxlength="<%=maxlength%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%>',
|
|
|
|
' rows=<%=rows ? rows : ""%>',
|
|
|
|
' <%=required ? "required" : ""%>><%-value%></textarea>',
|
|
|
|
' <% if (helpMessage && helpMessage.length) { %>',
|
|
|
|
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
|
|
|
|
' <% } %>',
|
|
|
|
'</div>'
|
|
|
|
].join("\n")),
|
|
|
|
onKeyUp: function(ev) {
|
|
|
|
if (this.key_timeout) {
|
|
|
|
clearTimeout(this.key_timeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.keyup_timeout = setTimeout(function() {
|
|
|
|
this.onChange(ev);
|
|
|
|
}.bind(this), 400);
|
|
|
|
}
|
|
|
|
});
|
2016-02-05 08:06:57 +00:00
|
|
|
|
2016-01-04 06:04:40 +00:00
|
|
|
/*
|
|
|
|
* Overriding the render function of the select control to allow us to use
|
|
|
|
* options as function, which should return array in the format of
|
|
|
|
* (label, value) pair.
|
|
|
|
*/
|
|
|
|
Backform.SelectControl.prototype.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
|
|
|
|
}),
|
2016-01-05 07:48:46 +00:00
|
|
|
evalF = function(f, d, m) {
|
2016-01-05 09:32:45 +00:00
|
|
|
return (_.isFunction(f) ? !!f.apply(d, [m]) : !!f);
|
2016-01-04 06:04:40 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Evaluate the disabled, visible, and required option
|
|
|
|
_.extend(data, {
|
2016-01-05 07:48:46 +00:00
|
|
|
disabled: evalF(data.disabled, data, this.model),
|
|
|
|
visible: evalF(data.visible, data, this.model),
|
|
|
|
required: evalF(data.required, data, this.model)
|
2016-01-04 06:04:40 +00:00
|
|
|
});
|
|
|
|
// Evaluation the options
|
|
|
|
if (_.isFunction(data.options)) {
|
|
|
|
try {
|
Improvised the 'transform/options' function usage with the Select2Cell.
The current implementaton binds the cell/control object, and the ajax
data in the asychronous Cells/Controls with the 'options' functions
extended from the Select2Cell.
The problem starts when we try to fetch the current model from that
options/transform/filter function to do some operation, which does not
require in most of the cases. Except the privileges control - where we
needed the current model for omitting the existing selected object
during transformation, and filtering.
In order resolved the issue, we need a common object, which is shared
among the Cell. In backgrid, the 'Column' object is mong the cell,
hence - implementation logic has been changed to bid the 'Column' object
with the 'options' function and, passed the 'Cell' object as an
arguments.
Because - we do use the common function 'transform' between 'Control'
and 'Cell', we needed make changes in the Select2Control to pass the
Control object as an arguments.
And, make the changes in the privileges control to use the new
implementation. The same logic is also required in some of the
operations, we will be/are working on the table/column nodes.
2016-04-08 05:30:48 +00:00
|
|
|
data.options = data.options(this)
|
2016-01-04 06:04:40 +00:00
|
|
|
} catch(e) {
|
|
|
|
// Do nothing
|
2016-01-09 12:29:56 +00:00
|
|
|
data.options = []
|
Improvised the 'transform/options' function usage with the Select2Cell.
The current implementaton binds the cell/control object, and the ajax
data in the asychronous Cells/Controls with the 'options' functions
extended from the Select2Cell.
The problem starts when we try to fetch the current model from that
options/transform/filter function to do some operation, which does not
require in most of the cases. Except the privileges control - where we
needed the current model for omitting the existing selected object
during transformation, and filtering.
In order resolved the issue, we need a common object, which is shared
among the Cell. In backgrid, the 'Column' object is mong the cell,
hence - implementation logic has been changed to bid the 'Column' object
with the 'options' function and, passed the 'Cell' object as an
arguments.
Because - we do use the common function 'transform' between 'Control'
and 'Cell', we needed make changes in the Select2Control to pass the
Control object as an arguments.
And, make the changes in the privileges control to use the new
implementation. The same logic is also required in some of the
operations, we will be/are working on the table/column nodes.
2016-04-08 05:30:48 +00:00
|
|
|
this.model.trigger('pgadmin-view:transform:error', this.model, this.field, e);
|
2016-01-04 06:04:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clean up first
|
|
|
|
this.$el.removeClass(Backform.hiddenClassname);
|
|
|
|
|
|
|
|
if (!data.visible)
|
|
|
|
this.$el.addClass(Backform.hiddenClassname);
|
|
|
|
|
|
|
|
this.$el.html(this.template(data)).addClass(field.name);
|
|
|
|
this.updateInvalid();
|
|
|
|
|
|
|
|
return this;
|
|
|
|
};
|
2016-03-24 07:23:11 +00:00
|
|
|
_.extend(Backform.SelectControl.prototype.defaults, {helpMessage: null});
|
2016-01-04 06:04:40 +00:00
|
|
|
|
2015-10-20 07:03:18 +00:00
|
|
|
var ReadonlyOptionControl = Backform.ReadonlyOptionControl = Backform.SelectControl.extend({
|
|
|
|
template: _.template([
|
|
|
|
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
|
|
|
|
'<div class="<%=Backform.controlsClassName%>">',
|
|
|
|
'<% for (var i=0; i < options.length; i++) { %>',
|
|
|
|
' <% var option = options[i]; %>',
|
2015-10-28 17:06:09 +00:00
|
|
|
' <% if (option.value === rawValue) { %>',
|
2015-10-20 07:03:18 +00:00
|
|
|
' <span class="<%=Backform.controlClassName%> uneditable-input" disabled><%-option.label%></span>',
|
|
|
|
' <% } %>',
|
|
|
|
'<% } %>',
|
2016-03-24 07:23:11 +00:00
|
|
|
'<% if (helpMessage && helpMessage.length) { %>',
|
|
|
|
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
|
|
|
|
'<% } %>',
|
2015-10-20 07:03:18 +00:00
|
|
|
'</div>'
|
|
|
|
].join("\n")),
|
|
|
|
events: {},
|
|
|
|
getValueFromDOM: function() {
|
|
|
|
return this.formatter.toRaw(this.$el.find("span").text(), this.model);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-02-22 11:30:17 +00:00
|
|
|
/*
|
|
|
|
* Override the function 'updateInvalid' of the radio control to resolve an
|
|
|
|
* issue, which will not render the error block multiple times for each
|
|
|
|
* options.
|
|
|
|
*/
|
|
|
|
_.extend(
|
|
|
|
Backform.RadioControl.prototype, {
|
|
|
|
updateInvalid: function() {
|
|
|
|
var self = this,
|
|
|
|
errorModel = this.model.errorModel;
|
|
|
|
|
|
|
|
if (!(errorModel instanceof Backbone.Model)) return this;
|
|
|
|
|
|
|
|
this.clearInvalid();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find input which have name attribute.
|
|
|
|
*/
|
|
|
|
this.$el.find(':input[name]').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;
|
|
|
|
|
|
|
|
self.$el.addClass(Backform.errorClassName).find(
|
|
|
|
'[type="radio"]'
|
|
|
|
).append(
|
|
|
|
$("<div></div>").addClass(
|
2016-03-09 16:03:24 +00:00
|
|
|
'pgadmin-control-error-message col-xs-offset-4 col-xs-8 col-xs-8 help-block'
|
2016-02-22 11:30:17 +00:00
|
|
|
).text(error));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2015-12-04 10:19:08 +00:00
|
|
|
// Requires the Bootstrap Switch to work.
|
|
|
|
var SwitchControl = Backform.SwitchControl = Backform.InputControl.extend({
|
|
|
|
defaults: {
|
|
|
|
label: "",
|
2015-12-17 13:11:36 +00:00
|
|
|
options: {
|
2016-03-23 10:24:19 +00:00
|
|
|
onText: 'Yes',
|
|
|
|
offText: 'No',
|
2015-12-17 13:11:36 +00:00
|
|
|
onColor: 'success',
|
2016-03-23 10:24:19 +00:00
|
|
|
offColor: 'primary',
|
2015-12-17 13:11:36 +00:00
|
|
|
size: 'small'
|
|
|
|
},
|
2016-03-24 07:23:11 +00:00
|
|
|
extraClasses: [],
|
|
|
|
helpMessage: null
|
2015-12-04 10:19:08 +00:00
|
|
|
},
|
|
|
|
template: _.template([
|
|
|
|
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
|
|
|
|
'<div class="<%=Backform.controlsClassName%>">',
|
|
|
|
' <div class="checkbox">',
|
|
|
|
' <label>',
|
2015-12-16 08:13:04 +00:00
|
|
|
' <input type="checkbox" class="<%=extraClasses.join(\' \')%>" name="<%=name%>" <%=value ? "checked=\'checked\'" : ""%> <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> />',
|
2015-12-04 10:19:08 +00:00
|
|
|
' </label>',
|
|
|
|
' </div>',
|
2016-03-24 07:23:11 +00:00
|
|
|
'</div>',
|
|
|
|
'<% if (helpMessage && helpMessage.length) { %>',
|
|
|
|
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
|
|
|
|
'<% } %>'
|
2015-12-04 10:19:08 +00:00
|
|
|
].join("\n")),
|
|
|
|
getValueFromDOM: function() {
|
|
|
|
return this.formatter.toRaw(
|
2015-12-16 08:13:04 +00:00
|
|
|
this.$input.prop('checked'),
|
2015-12-04 10:19:08 +00:00
|
|
|
this.model
|
|
|
|
);
|
|
|
|
},
|
2015-12-16 08:13:04 +00:00
|
|
|
events: {'switchChange.bootstrapSwitch': 'onChange'},
|
2015-12-04 10:19:08 +00:00
|
|
|
render: function() {
|
2016-03-22 14:28:13 +00:00
|
|
|
var field = _.defaults(this.field.toJSON(), this.defaults),
|
2015-12-04 10:19:08 +00:00
|
|
|
attributes = this.model.toJSON(),
|
|
|
|
attrArr = field.name.split('.'),
|
|
|
|
name = attrArr.shift(),
|
|
|
|
path = attrArr.join('.'),
|
2016-03-22 14:28:13 +00:00
|
|
|
rawValue = this.keyPathAccessor(attributes[name], path),
|
2016-03-23 07:20:49 +00:00
|
|
|
evalF = function(f, d, m) {
|
|
|
|
return (_.isFunction(f) ? !!f.apply(d, [m]) : !!f);
|
|
|
|
},
|
|
|
|
options = _.defaults({
|
|
|
|
disabled: evalF(field.disabled, field, this.model)
|
|
|
|
}, this.field.get('options'), this.defaults.options,
|
|
|
|
$.fn.bootstrapSwitch.defaults);
|
2015-12-04 10:19:08 +00:00
|
|
|
|
|
|
|
Backform.InputControl.prototype.render.apply(this, arguments);
|
2015-12-16 08:13:04 +00:00
|
|
|
this.$input = this.$el.find("input[type=checkbox]").first();
|
2015-12-04 10:19:08 +00:00
|
|
|
|
|
|
|
//Check & set additional properties
|
2015-12-17 13:11:36 +00:00
|
|
|
this.$input.bootstrapSwitch(
|
2016-03-22 14:28:13 +00:00
|
|
|
_.extend(options, {'state': rawValue})
|
2015-12-17 13:11:36 +00:00
|
|
|
);
|
|
|
|
|
2015-12-04 10:19:08 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
});
|
2015-10-20 07:03:18 +00:00
|
|
|
|
2015-06-30 05:51:55 +00:00
|
|
|
// Backform Dialog view (in bootstrap tabbular form)
|
|
|
|
// A collection of field models.
|
|
|
|
var Dialog = Backform.Dialog = Backform.Form.extend({
|
|
|
|
/* Array of objects having attributes [label, fields] */
|
|
|
|
schema: undefined,
|
2016-05-23 10:56:39 +00:00
|
|
|
tagName: "div",
|
2016-03-07 11:48:24 +00:00
|
|
|
legend: true,
|
2015-06-30 05:51:55 +00:00
|
|
|
className: function() {
|
|
|
|
return 'col-sm-12 col-md-12 col-lg-12 col-xs-12';
|
|
|
|
},
|
2015-10-29 20:32:40 +00:00
|
|
|
tabPanelClassName: function() {
|
|
|
|
return Backform.tabClassName;
|
|
|
|
},
|
2016-01-17 16:51:02 +00:00
|
|
|
tabIndex: 0,
|
2015-06-30 05:51:55 +00:00
|
|
|
initialize: function(opts) {
|
|
|
|
var s = opts.schema;
|
|
|
|
if (s && _.isArray(s)) {
|
|
|
|
this.schema = _.each(s, function(o) {
|
2015-07-14 03:59:44 +00:00
|
|
|
if (o.fields && !(o.fields instanceof Backbone.Collection))
|
2015-06-30 05:51:55 +00:00
|
|
|
o.fields = new Backform.Fields(o.fields);
|
|
|
|
o.cId = o.cId || _.uniqueId('pgC_');
|
|
|
|
o.hId = o.hId || _.uniqueId('pgH_');
|
|
|
|
o.disabled = o.disabled || false;
|
2016-03-07 11:48:24 +00:00
|
|
|
o.legend = opts.legend;
|
2015-06-30 05:51:55 +00:00
|
|
|
});
|
2015-10-29 20:32:40 +00:00
|
|
|
if (opts.tabPanelClassName && _.isFunction(opts.tabPanelClassName)) {
|
|
|
|
this.tabPanelClassName = opts.tabPanelClassName;
|
|
|
|
}
|
2015-06-30 05:51:55 +00:00
|
|
|
}
|
|
|
|
this.model.errorModel = opts.errorModel || this.model.errorModel || new Backbone.Model();
|
|
|
|
this.controls = [];
|
|
|
|
},
|
|
|
|
template: {
|
|
|
|
'header': _.template([
|
|
|
|
'<li role="presentation" <%=disabled ? "disabled" : ""%>>',
|
2015-10-29 20:32:40 +00:00
|
|
|
' <a data-toggle="tab" data-tab-index="<%=tabIndex%>" href="#<%=cId%>"',
|
2015-06-30 05:51:55 +00:00
|
|
|
' id="<%=hId%>" aria-controls="<%=cId%>">',
|
|
|
|
'<%=label%></a></li>'].join(" ")),
|
|
|
|
'panel': _.template(
|
2016-04-13 11:24:46 +00:00
|
|
|
'<div role="tabpanel" class="tab-pane <%=label%> col-sm-12 col-md-12 col-lg-12 col-xs-12 fade" id="<%=cId%>" aria-labelledby="<%=hId%>"></div>'
|
2015-06-30 05:51:55 +00:00
|
|
|
)},
|
|
|
|
render: function() {
|
|
|
|
this.cleanup();
|
|
|
|
|
|
|
|
var c = this.$el
|
|
|
|
.children().first().children('.active')
|
|
|
|
.first().attr('id'),
|
|
|
|
m = this.model,
|
|
|
|
controls = this.controls,
|
2015-10-29 20:32:40 +00:00
|
|
|
tmpls = this.template,
|
|
|
|
self = this,
|
2016-03-06 13:20:05 +00:00
|
|
|
idx=(this.tabIndex * 100),
|
|
|
|
evalF = function(f, d, m) {
|
|
|
|
return (_.isFunction(f) ? !!f.apply(d, [m]) : !!f);
|
|
|
|
};
|
2015-06-30 05:51:55 +00:00
|
|
|
|
|
|
|
this.$el
|
|
|
|
.empty()
|
|
|
|
.attr('role', 'tabpanel')
|
2016-01-17 16:51:02 +00:00
|
|
|
.attr('class', _.result(this, 'tabPanelClassName'));
|
2015-10-30 07:37:09 +00:00
|
|
|
m.panelEl = this.$el;
|
2015-06-30 05:51:55 +00:00
|
|
|
|
|
|
|
var tabHead = $('<ul class="nav nav-tabs" role="tablist"></ul>')
|
|
|
|
.appendTo(this.$el);
|
|
|
|
var tabContent = $('<ul class="tab-content col-sm-12 col-md-12 col-lg-12 col-xs-12"></ul>')
|
|
|
|
.appendTo(this.$el);
|
|
|
|
|
|
|
|
_.each(this.schema, function(o) {
|
2016-03-06 13:20:05 +00:00
|
|
|
idx++;
|
|
|
|
if (!o.version_compatible || !evalF(o.visible, o, m)) {
|
|
|
|
return;
|
|
|
|
}
|
2015-12-26 10:45:12 +00:00
|
|
|
var el = $((tmpls['panel'])(_.extend(o, {'tabIndex': idx})))
|
2015-06-30 05:51:55 +00:00
|
|
|
.appendTo(tabContent)
|
|
|
|
.removeClass('collapse').addClass('collapse'),
|
|
|
|
h = $((tmpls['header'])(o)).appendTo(tabHead);
|
|
|
|
|
|
|
|
o.fields.each(function(f) {
|
|
|
|
var cntr = new (f.get("control")) ({
|
|
|
|
field: f,
|
2015-12-26 10:45:12 +00:00
|
|
|
model: m,
|
|
|
|
dialog: self,
|
|
|
|
tabIndex: idx
|
2015-06-30 05:51:55 +00:00
|
|
|
});
|
|
|
|
el.append(cntr.render().$el);
|
|
|
|
controls.push(cntr);
|
|
|
|
});
|
2016-01-17 16:51:02 +00:00
|
|
|
|
2015-12-26 10:45:12 +00:00
|
|
|
tabHead.find('a[data-toggle="tab"]').off(
|
|
|
|
'shown.bs.tab'
|
|
|
|
).off('hidden.bs.tab').on(
|
|
|
|
'hidden.bs.tab', function() {
|
|
|
|
self.hidden_tab = $(this).data('tabIndex');
|
|
|
|
}).on('shown.bs.tab', function() {
|
2016-01-15 11:17:17 +00:00
|
|
|
var self = this;
|
|
|
|
self.shown_tab = $(self).data('tabIndex');
|
2015-12-26 10:45:12 +00:00
|
|
|
m.trigger('pg-property-tab-changed', {
|
|
|
|
'model': m, 'shown': self.shown_tab, 'hidden': self.hidden_tab,
|
2016-01-15 11:17:17 +00:00
|
|
|
'tab': self
|
2015-12-26 10:45:12 +00:00
|
|
|
});
|
|
|
|
});
|
2015-06-30 05:51:55 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
var makeActive = tabHead.find('[id="' + c + '"]').first();
|
|
|
|
if (makeActive.length == 1) {
|
|
|
|
makeActive.parent().addClass('active');
|
|
|
|
tabContent.find('#' + makeActive.attr("aria-controls"))
|
|
|
|
.addClass('in active');
|
|
|
|
} else {
|
|
|
|
tabHead.find('[role="presentation"]').first().addClass('active');
|
|
|
|
tabContent.find('[role="tabpanel"]').first().addClass('in active');
|
|
|
|
}
|
|
|
|
|
|
|
|
return this;
|
2016-04-15 10:44:01 +00:00
|
|
|
},
|
|
|
|
remove: function(opts) {
|
|
|
|
if (opts && opts.data) {
|
|
|
|
if (this.model) {
|
|
|
|
if (this.model.reset) {
|
2016-05-21 10:11:35 +00:00
|
|
|
this.model.reset({validate: false, silent: true, stop: true});
|
2016-04-15 10:44:01 +00:00
|
|
|
}
|
2016-05-21 10:11:35 +00:00
|
|
|
this.model.clear({validate: false, silent: true, stop: true});
|
2016-04-15 10:44:01 +00:00
|
|
|
delete (this.model);
|
|
|
|
}
|
|
|
|
if (this.errorModel) {
|
2016-05-21 10:11:35 +00:00
|
|
|
this.errorModel.clear({validate: false, silent: true, stop: true});
|
2016-04-15 10:44:01 +00:00
|
|
|
delete (this.errorModel);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.cleanup();
|
|
|
|
Backform.Form.prototype.remove.apply(this, arguments);
|
2015-06-30 05:51:55 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var Fieldset = Backform.Fieldset = Backform.Dialog.extend({
|
2016-01-17 16:51:02 +00:00
|
|
|
className: function() {
|
|
|
|
return 'set-group col-xs-12';
|
|
|
|
},
|
|
|
|
tabPanelClassName: function() {
|
|
|
|
return Backform.tabClassName;
|
|
|
|
},
|
|
|
|
fieldsetClass: Backform.setGroupClassName,
|
|
|
|
legendClass: 'badge',
|
|
|
|
contentClass: Backform.setGroupContentClassName + ' collapse in',
|
2015-06-30 05:51:55 +00:00
|
|
|
template: {
|
|
|
|
'header': _.template([
|
2016-01-17 16:51:02 +00:00
|
|
|
'<fieldset class="<%=fieldsetClass%>" <%=disabled ? "disabled" : ""%>>',
|
2016-03-07 11:48:24 +00:00
|
|
|
' <% if (legend != false) { %>',
|
2016-01-17 16:51:02 +00:00
|
|
|
' <legend class="<%=legendClass%>" <%=collapse ? "data-toggle=\'collapse\'" : ""%> data-target="#<%=cId%>"><%=collapse ? "<span class=\'caret\'></span>" : "" %><%=label%></legend>',
|
2016-03-07 11:48:24 +00:00
|
|
|
' <% } %>',
|
2015-06-30 05:51:55 +00:00
|
|
|
'</fieldset>'
|
|
|
|
].join("\n")),
|
|
|
|
'content': _.template(
|
2016-01-17 16:51:02 +00:00
|
|
|
' <div id="<%= cId %>" class="<%=contentClass%>"></div>'
|
2015-06-30 05:51:55 +00:00
|
|
|
)},
|
2016-01-17 16:51:02 +00:00
|
|
|
collapse: true,
|
2015-06-30 05:51:55 +00:00
|
|
|
render: function() {
|
|
|
|
this.cleanup();
|
|
|
|
|
|
|
|
var m = this.model,
|
|
|
|
$el = this.$el,
|
|
|
|
tmpl = this.template,
|
2016-01-17 16:51:02 +00:00
|
|
|
controls = this.controls,
|
|
|
|
data = {
|
|
|
|
'className': _.result(this, 'className'),
|
|
|
|
'fieldsetClass': _.result(this, 'fieldsetClass'),
|
|
|
|
'legendClass': _.result(this, 'legendClass'),
|
|
|
|
'contentClass': _.result(this, 'contentClass'),
|
|
|
|
'collapse': _.result(this, 'collapse')
|
2016-03-06 13:20:05 +00:00
|
|
|
},
|
|
|
|
idx=(this.tabIndex * 100),
|
|
|
|
evalF = function(f, d, m) {
|
|
|
|
return (_.isFunction(f) ? !!f.apply(d, [m]) : !!f);
|
2016-01-17 16:51:02 +00:00
|
|
|
};
|
2015-06-30 05:51:55 +00:00
|
|
|
|
|
|
|
this.$el.empty();
|
|
|
|
|
|
|
|
_.each(this.schema, function(o) {
|
2016-03-06 13:20:05 +00:00
|
|
|
idx++;
|
|
|
|
if (!o.version_compatible || !evalF(o.visible, o, m)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-06-30 05:51:55 +00:00
|
|
|
if (!o.fields)
|
|
|
|
return;
|
|
|
|
|
2016-01-17 16:51:02 +00:00
|
|
|
var d = _.extend({}, data, o),
|
|
|
|
h = $((tmpl['header'])(d)).appendTo($el),
|
|
|
|
el = $((tmpl['content'])(d)).appendTo(h);
|
2015-06-30 05:51:55 +00:00
|
|
|
|
|
|
|
o.fields.each(function(f) {
|
|
|
|
var cntr = new (f.get("control")) ({
|
|
|
|
field: f,
|
2016-03-06 13:20:05 +00:00
|
|
|
model: m,
|
|
|
|
tabIndex: idx
|
2015-06-30 05:51:55 +00:00
|
|
|
});
|
|
|
|
el.append(cntr.render().$el);
|
|
|
|
controls.push(cntr);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
getValueFromDOM: function() {
|
|
|
|
return "";
|
|
|
|
},
|
|
|
|
events: {}
|
|
|
|
});
|
|
|
|
|
2015-11-19 17:45:48 +00:00
|
|
|
var generateGridColumnsFromModel = Backform.generateGridColumnsFromModel =
|
2016-02-03 10:55:21 +00:00
|
|
|
function(node_info, m, type, cols, node) {
|
|
|
|
var groups = Backform.generateViewSchema(node_info, m, type, node, true, true),
|
2016-01-17 16:51:02 +00:00
|
|
|
schema = [],
|
|
|
|
columns = [],
|
|
|
|
func,
|
|
|
|
idx = 0;
|
2015-11-19 17:45:48 +00:00
|
|
|
|
2015-12-04 10:19:08 +00:00
|
|
|
// 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.
|
2015-12-17 13:00:39 +00:00
|
|
|
if (_.isNull(cols) || _.isUndefined(cols)) {
|
|
|
|
func = function(f) {
|
|
|
|
f.cell_priority = idx;
|
|
|
|
idx = idx + 1;
|
2015-12-04 10:19:08 +00:00
|
|
|
|
2015-12-17 13:00:39 +00:00
|
|
|
// 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;
|
2015-11-19 17:45:48 +00:00
|
|
|
}
|
2015-12-17 13:00:39 +00:00
|
|
|
};
|
|
|
|
} else if (_.isArray(cols)) {
|
|
|
|
func = function(f) {
|
|
|
|
f.cell_priority = _.indexOf(cols, f.name);
|
2015-12-04 10:19:08 +00:00
|
|
|
|
|
|
|
// 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
|
2015-12-17 13:00:39 +00:00
|
|
|
if ((!f.headerCell) && f.cellHeaderClasses) {
|
2015-12-04 10:19:08 +00:00
|
|
|
f.headerCell = Backgrid.Extension.CustomHeaderCell;
|
|
|
|
}
|
2015-12-17 13:00:39 +00:00
|
|
|
};
|
|
|
|
} else if(_.isObject(cols)) {
|
|
|
|
var tblCols = Object.keys(cols);
|
|
|
|
func = function(f) {
|
|
|
|
var val = (f.name in cols) && cols[f.name];
|
|
|
|
|
|
|
|
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 {
|
|
|
|
var 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)) {
|
|
|
|
var 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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2015-12-04 10:19:08 +00:00
|
|
|
|
2015-12-17 13:00:39 +00:00
|
|
|
// Prepare columns for backgrid
|
2016-01-17 17:01:49 +00:00
|
|
|
_.each(groups, function(group, key) {
|
|
|
|
_.each(group.fields, function(f) {
|
2016-01-17 16:51:02 +00:00
|
|
|
if (!f.cell) {
|
2015-12-17 13:00:39 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Check custom property in cols & if it is present then attach it to current cell
|
|
|
|
func(f);
|
|
|
|
if (f.cell_priority != -1) {
|
2015-11-19 17:45:48 +00:00
|
|
|
columns.push(f);
|
|
|
|
}
|
|
|
|
});
|
2016-01-17 17:01:49 +00:00
|
|
|
schema.push(group);
|
2015-11-17 06:21:09 +00:00
|
|
|
});
|
2015-11-19 17:45:48 +00:00
|
|
|
return {
|
|
|
|
'columns': _.sortBy(columns, function(c) {
|
|
|
|
return c.cell_priority;
|
|
|
|
}),
|
|
|
|
'schema': schema
|
|
|
|
};
|
2015-11-17 06:21:09 +00:00
|
|
|
};
|
|
|
|
|
2015-12-17 13:00:39 +00:00
|
|
|
var UniqueColCollectionControl = Backform.UniqueColCollectionControl = Backform.Control.extend({
|
|
|
|
initialize: function() {
|
|
|
|
Backform.Control.prototype.initialize.apply(this, arguments);
|
|
|
|
|
Resolved few intialization issue with Node model data, moved the
privileges functionality out of the backform.pgadmin.js to make it more
modular. Now - privileges will expect the privileges data in following
format:
<name_of_the_property> : [{
"privileges": [{
"privilege_type": <privilege_type>,
"privilege": true,
"with_grant": false
},
...
],
"grantee": <grantee>,
"grantor": <grantor>
},
...
]
Example:
acl": [{
"privileges": [{
"privilege_type": "CONNECT",
"privilege": true,
"with_grant": false
}],
"grantee": '',
"grantor": 'ashesh'
},{
"privileges": [{
"privilege_type": "CREATE",
"privilege": true,
"with_grant": false
},{
"privilege": true,
"privilege_type": "TEMPORARY",
"with_grant": false
}],
"grantee": test,
"grantor": ashesh
}]
2015-12-23 06:40:20 +00:00
|
|
|
var uniqueCol = this.field.get('uniqueCol') || [],
|
|
|
|
m = this.field.get('model'),
|
|
|
|
schema = m.prototype.schema || m.__super__.schema,
|
2016-01-09 12:29:56 +00:00
|
|
|
columns = [],
|
|
|
|
self = this;
|
Resolved few intialization issue with Node model data, moved the
privileges functionality out of the backform.pgadmin.js to make it more
modular. Now - privileges will expect the privileges data in following
format:
<name_of_the_property> : [{
"privileges": [{
"privilege_type": <privilege_type>,
"privilege": true,
"with_grant": false
},
...
],
"grantee": <grantee>,
"grantor": <grantor>
},
...
]
Example:
acl": [{
"privileges": [{
"privilege_type": "CONNECT",
"privilege": true,
"with_grant": false
}],
"grantee": '',
"grantor": 'ashesh'
},{
"privileges": [{
"privilege_type": "CREATE",
"privilege": true,
"with_grant": false
},{
"privilege": true,
"privilege_type": "TEMPORARY",
"with_grant": false
}],
"grantee": test,
"grantor": ashesh
}]
2015-12-23 06:40:20 +00:00
|
|
|
|
|
|
|
_.each(schema, function(s) {
|
|
|
|
columns.push(s.id);
|
|
|
|
});
|
2015-12-17 13:00:39 +00:00
|
|
|
|
|
|
|
// Check if unique columns provided are also in model attributes.
|
2016-01-09 12:29:56 +00:00
|
|
|
if (uniqueCol.length > _.intersection(columns, uniqueCol).length) {
|
2016-03-15 13:28:16 +00:00
|
|
|
errorMsg = "Developer: Unique columns [ "+_.difference(uniqueCol, columns)+" ] not found in collection model [ " + columns +" ]."
|
2015-12-17 13:00:39 +00:00
|
|
|
alert (errorMsg);
|
|
|
|
}
|
|
|
|
|
2016-01-09 12:29:56 +00:00
|
|
|
var collection = self.collection = self.model.get(self.field.get('name'));
|
|
|
|
|
2015-12-17 13:00:39 +00:00
|
|
|
if (!collection) {
|
2016-01-09 12:29:56 +00:00
|
|
|
collection = self.collection = new (pgAdmin.Browser.Node.Collection)(
|
|
|
|
null,
|
|
|
|
{
|
|
|
|
model: self.field.get('model'),
|
|
|
|
silent: true,
|
2016-04-29 10:26:51 +00:00
|
|
|
handler: self.model,
|
2016-01-15 13:40:29 +00:00
|
|
|
top: self.model.top || self.model,
|
2016-01-15 11:17:17 +00:00
|
|
|
attrName: self.field.get('name')
|
2016-01-09 12:29:56 +00:00
|
|
|
});
|
2015-12-17 13:00:39 +00:00
|
|
|
self.model.set(self.field.get('name'), collection, {silent: true});
|
|
|
|
}
|
2016-01-09 12:29:56 +00:00
|
|
|
|
2016-02-10 06:42:06 +00:00
|
|
|
if (this.field.get('version_compatible')) {
|
2016-01-12 06:31:45 +00:00
|
|
|
self.listenTo(collection, "add", self.collectionChanged);
|
|
|
|
self.listenTo(collection, "change", self.collectionChanged);
|
|
|
|
}
|
2015-12-17 13:00:39 +00:00
|
|
|
},
|
2016-03-07 10:35:01 +00:00
|
|
|
cleanup: function() {
|
|
|
|
this.stopListening(this.collection, "change", this.collectionChanged);
|
2016-01-09 12:29:56 +00:00
|
|
|
|
2016-02-10 06:42:06 +00:00
|
|
|
if (this.field.get('version_compatible')) {
|
2016-03-07 10:35:01 +00:00
|
|
|
this.stopListening(self.collection, "add", this.collectionChanged);
|
|
|
|
this.stopListening(self.collection, "change", this.collectionChanged);
|
2016-01-12 06:31:45 +00:00
|
|
|
}
|
2016-03-07 10:35:01 +00:00
|
|
|
if (this.grid) {
|
|
|
|
this.grid.remove();
|
|
|
|
delete this.grid;
|
|
|
|
}
|
|
|
|
this.$el.empty();
|
2016-01-09 12:29:56 +00:00
|
|
|
},
|
2015-12-17 13:00:39 +00:00
|
|
|
collectionChanged: function(newModel, coll, op) {
|
2016-01-09 12:29:56 +00:00
|
|
|
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);
|
2015-12-17 13:00:39 +00:00
|
|
|
}
|
2016-01-09 12:29:56 +00:00
|
|
|
});
|
|
|
|
if(uniqueChangedAttr.length == 0) {
|
2015-12-17 13:00:39 +00:00
|
|
|
return;
|
|
|
|
}
|
2016-01-09 12:29:56 +00:00
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
2015-12-17 13:00:39 +00:00
|
|
|
|
2016-01-09 12:29:56 +00:00
|
|
|
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)
|
2015-12-17 13:00:39 +00:00
|
|
|
}
|
2016-01-09 12:29:56 +00:00
|
|
|
});
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2016-01-15 13:40:29 +00:00
|
|
|
m.set(uniqueChangedAttr[0], m.previous(uniqueChangedAttr[0]));
|
2016-01-09 12:29:56 +00:00
|
|
|
}
|
|
|
|
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);
|
2015-12-17 13:00:39 +00:00
|
|
|
},
|
|
|
|
render: function() {
|
2016-03-07 10:35:01 +00:00
|
|
|
// Clean up existing elements
|
2016-04-13 14:54:17 +00:00
|
|
|
|
2016-04-01 09:55:53 +00:00
|
|
|
this.undelegateEvents();
|
2016-04-13 14:54:17 +00:00
|
|
|
this.$el.empty();
|
2016-03-07 10:35:01 +00:00
|
|
|
|
2015-12-17 13:00:39 +00:00
|
|
|
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
|
|
|
|
}),
|
2016-03-11 09:36:26 +00:00
|
|
|
evalF = function(f, d, m) {
|
|
|
|
return (_.isFunction(f) ? !!f.apply(d, [m]) : !!f);
|
2015-12-17 13:00:39 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Evaluate the disabled, visible, required, canAdd, & canDelete option
|
|
|
|
_.extend(data, {
|
2016-02-10 06:42:06 +00:00
|
|
|
disabled: (field.version_compatible &&
|
2016-03-11 09:36:26 +00:00
|
|
|
evalF.apply(this.field, [data.disabled, data, this.model])
|
2016-01-12 06:31:45 +00:00
|
|
|
),
|
2016-03-11 09:36:26 +00:00
|
|
|
visible: evalF.apply(this.field, [data.visible, data, this.model]),
|
|
|
|
required: evalF.apply(this.field, [data.required, data, this.model]),
|
2016-02-10 06:42:06 +00:00
|
|
|
canAdd: (field.version_compatible &&
|
2016-03-11 09:36:26 +00:00
|
|
|
evalF.apply(this.field, [data.canAdd, data, this.model])
|
2016-01-12 06:31:45 +00:00
|
|
|
),
|
2016-04-13 14:54:17 +00:00
|
|
|
canAddRow: data.canAddRow,
|
2016-03-11 09:36:26 +00:00
|
|
|
canDelete: evalF.apply(this.field, [data.canDelete, data, this.model]),
|
|
|
|
canEdit: evalF.apply(this.field, [data.canEdit, data, this.model])
|
2015-12-17 13:00:39 +00:00
|
|
|
});
|
2016-01-12 06:31:45 +00:00
|
|
|
_.extend(data, {add_label: "ADD"});
|
|
|
|
|
2016-03-07 10:35:01 +00:00
|
|
|
// This control is not visible, we should remove it.
|
|
|
|
if (!data.visible) {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2016-03-11 09:36:26 +00:00
|
|
|
this.control_data = _.clone(data);
|
|
|
|
|
2015-12-17 13:00:39 +00:00
|
|
|
// Show Backgrid Control
|
2016-01-09 12:29:56 +00:00
|
|
|
grid = this.showGridControl(data);
|
2015-12-17 13:00:39 +00:00
|
|
|
|
|
|
|
this.$el.html(grid).addClass(field.name);
|
|
|
|
this.updateInvalid();
|
|
|
|
|
2016-04-01 09:55:53 +00:00
|
|
|
this.delegateEvents();
|
2015-12-17 13:00:39 +00:00
|
|
|
return this;
|
|
|
|
},
|
|
|
|
showGridControl: function(data) {
|
2016-01-12 06:31:45 +00:00
|
|
|
var gridHeader = _.template([
|
|
|
|
'<div class="subnode-header">',
|
|
|
|
' <label class="control-label col-sm-4"><%-label%></label>',
|
2016-01-12 08:10:32 +00:00
|
|
|
' <button class="btn-sm btn-default add" <%=canAdd ? "" : "disabled=\'disabled\'"%>><%-add_label%></buttton>',
|
2016-01-12 06:31:45 +00:00
|
|
|
'</div>'].join("\n")),
|
2016-01-21 09:11:14 +00:00
|
|
|
gridBody = $('<div class="pgadmin-control-group backgrid form-group col-xs-12 object subnode"></div>').append(
|
2016-01-12 06:31:45 +00:00
|
|
|
gridHeader(data)
|
|
|
|
);
|
2015-12-17 13:00:39 +00:00
|
|
|
|
2016-01-09 12:29:56 +00:00
|
|
|
if (!(data.subnode)) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
2015-12-17 13:00:39 +00:00
|
|
|
var subnode = data.subnode.schema ? data.subnode : data.subnode.prototype,
|
|
|
|
gridSchema = Backform.generateGridColumnsFromModel(
|
|
|
|
data.node_info, subnode, this.field.get('mode'), data.columns
|
|
|
|
),
|
|
|
|
self = this;
|
|
|
|
|
|
|
|
// Set visibility of Add button
|
2016-01-12 06:31:45 +00:00
|
|
|
if (data.mode == 'properties') {
|
2015-12-17 13:00:39 +00:00
|
|
|
$(gridBody).find("button.add").remove();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert Delete Cell into Grid
|
2016-01-12 06:31:45 +00:00
|
|
|
if (!data.disabled && data.canDelete) {
|
2015-12-17 13:00:39 +00:00
|
|
|
gridSchema.columns.unshift({
|
|
|
|
name: "pg-backform-delete", label: "",
|
|
|
|
cell: Backgrid.Extension.DeleteCell,
|
2016-04-13 14:54:17 +00:00
|
|
|
editable: false, cell_priority: -1,
|
|
|
|
canDeleteRow: data.canDeleteRow
|
2015-12-17 13:00:39 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-03-04 11:00:17 +00:00
|
|
|
// 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,
|
2016-04-13 14:54:17 +00:00
|
|
|
cell_priority: -2, canEditRow: data.canEditRow
|
2016-03-04 11:00:17 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-12-17 13:00:39 +00:00
|
|
|
var collection = this.model.get(data.name);
|
2016-01-12 06:31:45 +00:00
|
|
|
|
2016-03-22 17:04:46 +00:00
|
|
|
var cellEditing = function(args){
|
|
|
|
var self = 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 = self.indexOf(m);
|
|
|
|
if (idx > -1) {
|
|
|
|
var row = grid.body.rows[idx],
|
|
|
|
editCell = row.$el.find(".subnode-edit-in-process").parent();
|
|
|
|
// Only close row if it's open.
|
|
|
|
if (editCell.length > 0){
|
|
|
|
var event = new Event('click');
|
|
|
|
editCell[0].dispatchEvent(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
// Listen for any row which is about to enter in edit mode.
|
|
|
|
collection.on( "enteringEditMode", cellEditing, collection);
|
|
|
|
|
2015-12-17 13:00:39 +00:00
|
|
|
// Initialize a new Grid instance
|
2016-01-09 12:29:56 +00:00
|
|
|
var grid = self.grid = new Backgrid.Grid({
|
|
|
|
columns: gridSchema.columns,
|
|
|
|
collection: collection,
|
|
|
|
className: "backgrid table-bordered"
|
2015-12-17 13:00:39 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// Render subNode grid
|
|
|
|
subNodeGrid = grid.render().$el;
|
|
|
|
|
|
|
|
// Combine Edit and Delete Cell
|
|
|
|
if (data.canDelete && data.canEdit) {
|
|
|
|
$(subNodeGrid).find("th.pg-backform-delete").remove();
|
2016-03-04 11:00:17 +00:00
|
|
|
$(subNodeGrid).find("th.pg-backform-edit").attr("colspan", "2");
|
2015-12-17 13:00:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$dialog = gridBody.append(subNodeGrid);
|
|
|
|
|
|
|
|
// Add button callback
|
|
|
|
if (!(data.disabled || data.canAdd == false)) {
|
|
|
|
$dialog.find('button.add').first().click(function(e) {
|
2016-01-09 12:29:56 +00:00
|
|
|
e.preventDefault();
|
2016-04-13 14:54:17 +00:00
|
|
|
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 editCell = row.$el.find(".subnode-edit-in-process").parent();
|
|
|
|
// Only close row if it's open.
|
|
|
|
if (editCell.length > 0){
|
|
|
|
var event = new Event('click');
|
|
|
|
editCell[0].dispatchEvent(event);
|
|
|
|
}
|
|
|
|
});
|
2016-03-22 17:04:46 +00:00
|
|
|
|
2016-04-13 14:54:17 +00:00
|
|
|
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, key) {
|
|
|
|
modelValues.push(val);
|
|
|
|
})
|
|
|
|
if(!_.some(modelValues, _.identity)) {
|
|
|
|
isEmpty = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if(isEmpty) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-01-09 12:29:56 +00:00
|
|
|
}
|
2015-12-17 13:00:39 +00:00
|
|
|
|
2016-04-13 14:54:17 +00:00
|
|
|
$(grid.body.$el.find($("tr.new"))).removeClass("new")
|
|
|
|
var m = new (data.model) (null, {
|
|
|
|
silent: true,
|
2016-04-29 10:26:51 +00:00
|
|
|
handler: collection,
|
2016-04-13 14:54:17 +00:00
|
|
|
top: self.model.top || self.model,
|
2016-04-29 10:26:51 +00:00
|
|
|
collection: collection,
|
|
|
|
node_info: self.model.node_info
|
2016-04-13 14:54:17 +00:00
|
|
|
});
|
|
|
|
collection.add(m);
|
2015-12-17 13:00:39 +00:00
|
|
|
|
2016-04-13 14:54:17 +00:00
|
|
|
var idx = collection.indexOf(m),
|
|
|
|
newRow = grid.body.rows[idx].$el;
|
2016-01-09 12:29:56 +00:00
|
|
|
|
2016-04-13 14:54:17 +00:00
|
|
|
newRow.addClass("new");
|
|
|
|
$(newRow).pgMakeVisible('backform-tab');
|
2016-01-09 12:29:56 +00:00
|
|
|
|
2016-04-13 14:54:17 +00:00
|
|
|
return false;
|
|
|
|
}
|
2015-12-17 13:00:39 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return $dialog;
|
2016-01-11 16:02:03 +00:00
|
|
|
},
|
2016-04-07 10:51:16 +00:00
|
|
|
clearInvalid: function() {
|
|
|
|
this.$el.removeClass("subnode-error");
|
|
|
|
this.$el.find(".pgadmin-control-error-message").remove();
|
|
|
|
return this;
|
|
|
|
},
|
2016-01-11 16:02:03 +00:00
|
|
|
updateInvalid: function() {
|
|
|
|
var self = this,
|
|
|
|
errorModel = this.model.errorModel;
|
2015-12-17 13:00:39 +00:00
|
|
|
|
2016-01-11 16:02:03 +00:00
|
|
|
if (!(errorModel instanceof Backbone.Model)) return this;
|
|
|
|
|
|
|
|
this.clearInvalid();
|
|
|
|
|
|
|
|
this.$el.find('.subnode-body').each(function(ix, el) {
|
2016-01-12 06:31:45 +00:00
|
|
|
var error = self.keyPathAccessor(errorModel.toJSON(), self.field.get('name'));
|
2016-01-11 16:02:03 +00:00
|
|
|
|
|
|
|
if (_.isEmpty(error)) return;
|
|
|
|
|
2016-04-07 10:51:16 +00:00
|
|
|
self.$el.addClass("subnode-error").append(
|
2016-03-09 16:03:24 +00:00
|
|
|
$("<div></div>").addClass('pgadmin-control-error-message col-xs-offset-4 col-xs-8 help-block').text(error)
|
2016-01-11 16:02:03 +00:00
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
2015-12-17 13:00:39 +00:00
|
|
|
});
|
|
|
|
|
2015-10-28 17:06:09 +00:00
|
|
|
var SubNodeCollectionControl = Backform.SubNodeCollectionControl = Backform.Control.extend({
|
|
|
|
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
|
|
|
|
}),
|
2016-03-23 07:20:49 +00:00
|
|
|
evalF = function(f, d, m) {
|
|
|
|
return (_.isFunction(f) ? !!f.apply(d, [m]) : !!f);
|
2015-10-28 17:06:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Evaluate the disabled, visible, required, canAdd, cannEdit & canDelete option
|
|
|
|
_.extend(data, {
|
2016-03-23 07:20:49 +00:00
|
|
|
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),
|
2016-04-13 14:54:17 +00:00
|
|
|
canAddRow: data.canAddRow,
|
2016-03-23 07:20:49 +00:00
|
|
|
canEdit: evalF(data.canEdit, data, this.model),
|
|
|
|
canDelete: evalF(data.canDelete, data, this.model)
|
2015-10-28 17:06:09 +00:00
|
|
|
});
|
|
|
|
// Show Backgrid Control
|
|
|
|
grid = (data.subnode == undefined) ? "" : this.showGridControl(data);
|
|
|
|
|
|
|
|
this.$el.html(grid).addClass(field.name);
|
|
|
|
this.updateInvalid();
|
|
|
|
|
|
|
|
return this;
|
|
|
|
},
|
2015-12-17 13:00:39 +00:00
|
|
|
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('.'),
|
|
|
|
name = attrArr.shift(),
|
|
|
|
path = attrArr.join('.'),
|
|
|
|
error = self.keyPathAccessor(errorModel.toJSON(), path);
|
|
|
|
|
|
|
|
if (_.isEmpty(error)) return;
|
|
|
|
|
2016-04-07 10:51:16 +00:00
|
|
|
self.$el.addClass('subnode-error').append(
|
2016-03-09 16:03:24 +00:00
|
|
|
$("<div></div>").addClass('pgadmin-control-error-message col-xs-offset-4 col-xs-8 help-block').text(error)
|
2015-12-17 13:00:39 +00:00
|
|
|
);
|
|
|
|
},
|
2016-04-07 10:51:16 +00:00
|
|
|
clearInvalid: function() {
|
|
|
|
this.$el.removeClass('subnode-error');
|
|
|
|
this.$el.find(".pgadmin-control-error-message").remove();
|
|
|
|
return this;
|
|
|
|
},
|
2015-10-28 17:06:09 +00:00
|
|
|
showGridControl: function(data) {
|
|
|
|
var gridHeader = ["<div class='subnode-header'>",
|
|
|
|
" <label class='control-label col-sm-4'>" + data.label + "</label>" ,
|
2016-04-01 09:55:53 +00:00
|
|
|
" <button class='btn-sm btn-default add'>ADD</buttton>",
|
2015-10-28 17:06:09 +00:00
|
|
|
"</div>"].join("\n");
|
2015-10-29 20:32:40 +00:00
|
|
|
gridBody = $("<div class='pgadmin-control-group backgrid form-group col-xs-12 object subnode'></div>").append(gridHeader);
|
2015-10-28 17:06:09 +00:00
|
|
|
|
|
|
|
var subnode = data.subnode.schema ? data.subnode : data.subnode.prototype,
|
2015-11-17 06:21:09 +00:00
|
|
|
gridSchema = Backform.generateGridColumnsFromModel(
|
2016-02-03 10:55:21 +00:00
|
|
|
data.node_info, subnode, this.field.get('mode'), data.columns, data.schema_node
|
Resolved few intialization issue with Node model data, moved the
privileges functionality out of the backform.pgadmin.js to make it more
modular. Now - privileges will expect the privileges data in following
format:
<name_of_the_property> : [{
"privileges": [{
"privilege_type": <privilege_type>,
"privilege": true,
"with_grant": false
},
...
],
"grantee": <grantee>,
"grantor": <grantor>
},
...
]
Example:
acl": [{
"privileges": [{
"privilege_type": "CONNECT",
"privilege": true,
"with_grant": false
}],
"grantee": '',
"grantor": 'ashesh'
},{
"privileges": [{
"privilege_type": "CREATE",
"privilege": true,
"with_grant": false
},{
"privilege": true,
"privilege_type": "TEMPORARY",
"with_grant": false
}],
"grantee": test,
"grantor": ashesh
}]
2015-12-23 06:40:20 +00:00
|
|
|
), self = this,
|
|
|
|
pgBrowser = window.pgAdmin.Browser;
|
2015-10-28 17:06:09 +00:00
|
|
|
|
|
|
|
// 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) {
|
2015-11-17 06:21:09 +00:00
|
|
|
gridSchema.columns.unshift({
|
2015-10-28 17:06:09 +00:00
|
|
|
name: "pg-backform-delete", label: "",
|
|
|
|
cell: Backgrid.Extension.DeleteCell,
|
2016-04-13 14:54:17 +00:00
|
|
|
editable: false, cell_priority: -1,
|
|
|
|
canDeleteRow: data.canDeleteRow
|
2015-10-28 17:06:09 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert Edit Cell into Grid
|
|
|
|
if (data.disabled == false && data.canEdit) {
|
|
|
|
var editCell = Backgrid.Extension.ObjectCell.extend({
|
2015-11-17 06:21:09 +00:00
|
|
|
schema: gridSchema.schema
|
2016-03-10 12:03:33 +00:00
|
|
|
}),
|
|
|
|
canEdit = self.field.has('canEdit') &&
|
|
|
|
self.field.get('canEdit') || true;
|
2015-10-28 17:06:09 +00:00
|
|
|
|
2015-11-17 06:21:09 +00:00
|
|
|
gridSchema.columns.unshift({
|
2015-10-28 17:06:09 +00:00
|
|
|
name: "pg-backform-edit", label: "", cell : editCell,
|
2016-04-13 14:54:17 +00:00
|
|
|
cell_priority: -2, editable: canEdit,
|
|
|
|
canEditRow: data.canEditRow
|
2015-10-28 17:06:09 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
Resolved few intialization issue with Node model data, moved the
privileges functionality out of the backform.pgadmin.js to make it more
modular. Now - privileges will expect the privileges data in following
format:
<name_of_the_property> : [{
"privileges": [{
"privilege_type": <privilege_type>,
"privilege": true,
"with_grant": false
},
...
],
"grantee": <grantee>,
"grantor": <grantor>
},
...
]
Example:
acl": [{
"privileges": [{
"privilege_type": "CONNECT",
"privilege": true,
"with_grant": false
}],
"grantee": '',
"grantor": 'ashesh'
},{
"privileges": [{
"privilege_type": "CREATE",
"privilege": true,
"with_grant": false
},{
"privilege": true,
"privilege_type": "TEMPORARY",
"with_grant": false
}],
"grantee": test,
"grantor": ashesh
}]
2015-12-23 06:40:20 +00:00
|
|
|
var collection = self.model.get(data.name);
|
|
|
|
|
|
|
|
if (!collection) {
|
|
|
|
collection = new (pgBrowser.Node.Collection)(null, {
|
|
|
|
handler: self.model.handler || self,
|
2016-04-29 10:26:51 +00:00
|
|
|
model: data.model, top: self.model.top || self.model,
|
Resolved few intialization issue with Node model data, moved the
privileges functionality out of the backform.pgadmin.js to make it more
modular. Now - privileges will expect the privileges data in following
format:
<name_of_the_property> : [{
"privileges": [{
"privilege_type": <privilege_type>,
"privilege": true,
"with_grant": false
},
...
],
"grantee": <grantee>,
"grantor": <grantor>
},
...
]
Example:
acl": [{
"privileges": [{
"privilege_type": "CONNECT",
"privilege": true,
"with_grant": false
}],
"grantee": '',
"grantor": 'ashesh'
},{
"privileges": [{
"privilege_type": "CREATE",
"privilege": true,
"with_grant": false
},{
"privilege": true,
"privilege_type": "TEMPORARY",
"with_grant": false
}],
"grantee": test,
"grantor": ashesh
}]
2015-12-23 06:40:20 +00:00
|
|
|
silent: true
|
|
|
|
});
|
|
|
|
self.model.set(data.name, collection, {silent: true});
|
|
|
|
}
|
2016-03-22 17:04:46 +00:00
|
|
|
|
|
|
|
var cellEditing = function(args){
|
|
|
|
var self = 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 = self.indexOf(m);
|
|
|
|
if (idx > -1) {
|
|
|
|
var row = grid.body.rows[idx],
|
|
|
|
editCell = row.$el.find(".subnode-edit-in-process").parent();
|
|
|
|
// Only close row if it's open.
|
|
|
|
if (editCell.length > 0){
|
|
|
|
var event = new Event('click');
|
|
|
|
editCell[0].dispatchEvent(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
// Listen for any row which is about to enter in edit mode.
|
|
|
|
collection.on( "enteringEditMode", cellEditing, collection);
|
|
|
|
|
2015-10-28 17:06:09 +00:00
|
|
|
// Initialize a new Grid instance
|
2016-01-09 12:29:56 +00:00
|
|
|
var grid = self.grid = new Backgrid.Grid({
|
2015-11-17 06:21:09 +00:00
|
|
|
columns: gridSchema.columns,
|
2015-12-17 13:00:39 +00:00
|
|
|
collection: collection,
|
2015-10-28 17:06:09 +00:00
|
|
|
className: "backgrid table-bordered"
|
|
|
|
});
|
|
|
|
|
|
|
|
// Render subNode grid
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
|
|
|
$dialog = gridBody.append(subNodeGrid);
|
|
|
|
|
|
|
|
// Add button callback
|
|
|
|
$dialog.find('button.add').click(function(e) {
|
2016-04-13 14:54:17 +00:00
|
|
|
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 editCell = row.$el.find(".subnode-edit-in-process").parent();
|
|
|
|
// Only close row if it's open.
|
|
|
|
if (editCell.length > 0){
|
|
|
|
var event = new Event('click');
|
|
|
|
editCell[0].dispatchEvent(event);
|
|
|
|
}
|
|
|
|
});
|
2016-03-22 17:04:46 +00:00
|
|
|
|
2016-04-13 14:54:17 +00:00
|
|
|
grid.insertRow({});
|
2016-03-22 17:04:46 +00:00
|
|
|
|
2016-04-13 14:54:17 +00:00
|
|
|
var newRow = $(grid.body.rows[collection.length - 1].$el);
|
|
|
|
newRow.attr("class", "new").click(function(e) {
|
|
|
|
$(this).attr("class", "editable");
|
|
|
|
});
|
|
|
|
$(newRow).pgMakeVisible('backform-tab');
|
|
|
|
return false;
|
|
|
|
}
|
2015-10-28 17:06:09 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return $dialog;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2015-12-26 10:45:12 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
var SqlTabControl = Backform.SqlTabControl = Backform.Control.extend({
|
|
|
|
defaults: {
|
|
|
|
label: "",
|
2016-04-13 11:24:46 +00:00
|
|
|
controlsClassName: "pgadmin-controls col-sm-12 SQL",
|
2015-12-26 10:45:12 +00:00
|
|
|
extraClasses: [],
|
|
|
|
helpMessage: null
|
|
|
|
},
|
|
|
|
template: _.template([
|
|
|
|
'<div class="<%=controlsClassName%>">',
|
|
|
|
' <textarea class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%>><%-value%></textarea>',
|
|
|
|
' <% if (helpMessage && helpMessage.length) { %>',
|
|
|
|
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
|
|
|
|
' <% } %>',
|
|
|
|
'</div>'
|
|
|
|
].join("\n")),
|
|
|
|
/*
|
|
|
|
* Initialize the SQL Tab control properly
|
|
|
|
*/
|
|
|
|
initialize: function(o) {
|
|
|
|
Backform.Control.prototype.initialize.apply(this, arguments);
|
|
|
|
|
|
|
|
// Save the required information for using it later.
|
|
|
|
this.dialog = o.dialog;
|
|
|
|
this.tabIndex = o.tabIndex;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We will listen to the tab change event to check, if the SQL tab has
|
|
|
|
* been clicked or, not.
|
|
|
|
*/
|
|
|
|
this.model.on('pg-property-tab-changed', this.onTabChange, this);
|
|
|
|
},
|
|
|
|
getValueFromDOM: function() {
|
|
|
|
return this.formatter.toRaw(this.$el.find("textarea").val(), this.model);
|
|
|
|
},
|
|
|
|
render: function() {
|
2016-04-22 09:54:18 +00:00
|
|
|
if (this.sqlCtrl) {
|
|
|
|
delete this.sqlCtrl;
|
|
|
|
this.sqlCtrl = null;
|
|
|
|
this.$el.empty();
|
|
|
|
}
|
2015-12-26 10:45:12 +00:00
|
|
|
// Use the Backform Control's render function
|
|
|
|
Backform.Control.prototype.render.apply(this, arguments);
|
|
|
|
|
2016-04-22 09:54:18 +00:00
|
|
|
this.sqlCtrl = CodeMirror.fromTextArea(
|
2015-12-26 10:45:12 +00:00
|
|
|
(this.$el.find("textarea")[0]), {
|
|
|
|
lineNumbers: true,
|
2016-04-22 09:54:18 +00:00
|
|
|
lineWrapping: true,
|
2016-04-21 19:13:00 +00:00
|
|
|
mode: "text/x-pgsql",
|
2015-12-26 10:45:12 +00:00
|
|
|
readOnly: true
|
|
|
|
});
|
2016-04-22 09:54:18 +00:00
|
|
|
|
2015-12-26 10:45:12 +00:00
|
|
|
return this;
|
|
|
|
},
|
2016-01-15 11:17:17 +00:00
|
|
|
onTabChange: function(obj) {
|
2015-12-26 10:45:12 +00:00
|
|
|
|
|
|
|
// Fetch the information only if the SQL tab is visible at the moment.
|
2016-01-15 11:17:17 +00:00
|
|
|
if (this.dialog && obj.shown == this.tabIndex) {
|
2015-12-26 10:45:12 +00:00
|
|
|
|
2016-05-10 07:04:20 +00:00
|
|
|
// We will send a request to the sever only if something has changed
|
|
|
|
// in a model and also it does not contain any error.
|
2016-01-15 11:17:17 +00:00
|
|
|
if(this.model.sessChanged()) {
|
2016-05-10 07:04:20 +00:00
|
|
|
if (_.size(this.model.errorModel.attributes) == 0) {
|
|
|
|
var self = this,
|
|
|
|
node = self.field.get('schema_node'),
|
|
|
|
msql_url = node.generate_url.apply(
|
|
|
|
node, [
|
|
|
|
null, 'msql', this.field.get('node_data'), !self.model.isNew(),
|
|
|
|
this.field.get('node_info')
|
|
|
|
]);
|
|
|
|
|
|
|
|
// Fetching the modified SQL
|
|
|
|
self.model.trigger('pgadmin-view:msql:fetching', self.method, node);
|
|
|
|
|
|
|
|
$.ajax({
|
|
|
|
url: msql_url,
|
|
|
|
type: 'GET',
|
|
|
|
cache: false,
|
|
|
|
data: self.model.toJSON(true, 'GET'),
|
|
|
|
dataType: "json",
|
|
|
|
contentType: "application/json"
|
|
|
|
}).done(function(res) {
|
|
|
|
self.sqlCtrl.clearHistory();
|
|
|
|
self.sqlCtrl.setValue(res.data);
|
|
|
|
}).fail(function() {
|
|
|
|
self.model.trigger('pgadmin-view:msql:error', self.method, node, arguments);
|
|
|
|
}).always(function() {
|
|
|
|
self.model.trigger('pgadmin-view:msql:fetched', self.method, node, arguments);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
this.sqlCtrl.clearHistory();
|
|
|
|
this.sqlCtrl.setValue('-- ' + window.pgAdmin.Browser.messages.SQL_INCOMPLETE);
|
|
|
|
}
|
2015-12-26 10:45:12 +00:00
|
|
|
} else {
|
2016-04-22 09:54:18 +00:00
|
|
|
this.sqlCtrl.clearHistory();
|
2016-05-10 07:04:20 +00:00
|
|
|
this.sqlCtrl.setValue('-- ' + window.pgAdmin.Browser.messages.SQL_NO_CHANGE);
|
2015-12-26 10:45:12 +00:00
|
|
|
}
|
2016-04-22 09:54:18 +00:00
|
|
|
this.sqlCtrl.refresh.apply(this.sqlCtrl);
|
2015-12-26 10:45:12 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
remove: function() {
|
2016-04-22 09:54:18 +00:00
|
|
|
if (this.sqlCtrl) {
|
|
|
|
delete this.sqlCtrl;
|
|
|
|
this.sqlCtrl = null;
|
|
|
|
this.$el.empty();
|
|
|
|
}
|
2015-12-26 10:45:12 +00:00
|
|
|
this.model.off('pg-property-tab-changed', this.onTabChange, this);
|
|
|
|
Backform.Control.__super__.remove.apply(this, arguments);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-02-05 08:06:57 +00:00
|
|
|
/*
|
|
|
|
* Integer input Control functionality just like backgrid
|
|
|
|
*/
|
2016-01-05 11:19:58 +00:00
|
|
|
var IntegerControl = Backform.IntegerControl = Backform.InputControl.extend({
|
|
|
|
defaults: {
|
|
|
|
type: "number",
|
|
|
|
label: "",
|
|
|
|
min: undefined,
|
|
|
|
max: undefined,
|
|
|
|
maxlength: 255,
|
|
|
|
extraClasses: [],
|
|
|
|
helpMessage: null
|
|
|
|
},
|
|
|
|
template: _.template([
|
|
|
|
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
|
|
|
|
'<div class="<%=Backform.controlsClassName%>">',
|
|
|
|
' <input type="<%=type%>" class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>" min="<%=min%>" max="<%=max%>"maxlength="<%=maxlength%>" value="<%-value%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> />',
|
|
|
|
' <% if (helpMessage && helpMessage.length) { %>',
|
|
|
|
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
|
|
|
|
' <% } %>',
|
|
|
|
'</div>'
|
|
|
|
].join("\n")),
|
|
|
|
events: {
|
|
|
|
"change input": "checkInt",
|
|
|
|
"focus input": "clearInvalid"
|
|
|
|
},
|
|
|
|
checkInt: function(e) {
|
|
|
|
var field = _.defaults(this.field.toJSON(), this.defaults),
|
|
|
|
attrArr = this.field.get("name").split('.'),
|
|
|
|
name = attrArr.shift(),
|
|
|
|
value = this.getValueFromDOM(),
|
|
|
|
min_value = field.min,
|
|
|
|
max_value = field.max,
|
2016-01-07 13:20:11 +00:00
|
|
|
isValid = true,
|
2016-01-05 11:19:58 +00:00
|
|
|
intPattern = new RegExp("^-?[0-9]*$"),
|
|
|
|
isMatched = intPattern.test(value);
|
|
|
|
|
|
|
|
// Below logic will validate input
|
|
|
|
if (!isMatched) {
|
2016-01-07 13:20:11 +00:00
|
|
|
isValid = false;
|
2016-01-05 11:19:58 +00:00
|
|
|
this.model.errorModel.unset(name);
|
|
|
|
this.model.errorModel.set(
|
|
|
|
name,
|
|
|
|
S(pgAdmin.Browser.messages.MUST_BE_INT).sprintf(
|
|
|
|
field.label
|
|
|
|
).value()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Below will check if entered value is in-between min & max range
|
2016-01-07 13:20:11 +00:00
|
|
|
if (isValid && (!_.isUndefined(min_value) && value < min_value)) {
|
|
|
|
isValid = false;
|
2016-01-05 11:19:58 +00:00
|
|
|
this.model.errorModel.unset(name);
|
|
|
|
this.model.errorModel.set(
|
|
|
|
name,
|
|
|
|
S(pgAdmin.Browser.messages.MUST_GR_EQ).sprintf(
|
|
|
|
field.label,
|
|
|
|
min_value
|
|
|
|
).value()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-01-07 13:20:11 +00:00
|
|
|
if (isValid && (!_.isUndefined(max_value) && value > max_value)) {
|
|
|
|
isValid = false;
|
2016-01-05 11:19:58 +00:00
|
|
|
this.model.errorModel.unset(name);
|
|
|
|
this.model.errorModel.set(
|
|
|
|
name,
|
|
|
|
S(pgAdmin.Browser.messages.MUST_LESS_EQ).sprintf(
|
|
|
|
field.label,
|
|
|
|
max_value
|
|
|
|
).value()
|
|
|
|
);
|
|
|
|
}
|
2016-01-07 13:20:11 +00:00
|
|
|
|
2016-01-15 13:40:29 +00:00
|
|
|
// After validation we need to set that value into model (only if all flags are true)
|
2016-01-07 13:20:11 +00:00
|
|
|
if (isValid) {
|
|
|
|
this.stopListening(this.model, "change:" + name, this.render);
|
|
|
|
this.model.set(name, value);
|
|
|
|
this.listenTo(this.model, "change:" + name, this.render);
|
2016-02-22 11:48:02 +00:00
|
|
|
} else {
|
|
|
|
if (this.model.collection || this.model.handler) {
|
|
|
|
(this.model.collection || this.model.handler).trigger(
|
|
|
|
'pgadmin-session:model:invalid', this.model.errorModel.get(name), this.model
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
(this.model).trigger(
|
|
|
|
'pgadmin-session:invalid', this.model.errorModel.get(name), this.model
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Numeric input Control functionality just like backgrid
|
|
|
|
*/
|
|
|
|
var NumericControl = Backform.NumericControl = Backform.InputControl.extend({
|
|
|
|
defaults: {
|
|
|
|
type: "number",
|
|
|
|
label: "",
|
|
|
|
min: undefined,
|
|
|
|
max: undefined,
|
|
|
|
maxlength: 255,
|
|
|
|
extraClasses: [],
|
|
|
|
helpMessage: null
|
|
|
|
},
|
|
|
|
template: _.template([
|
|
|
|
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
|
|
|
|
'<div class="<%=Backform.controlsClassName%>">',
|
|
|
|
' <input type="<%=type%>" class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>" min="<%=min%>" max="<%=max%>"maxlength="<%=maxlength%>" value="<%-value%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> />',
|
|
|
|
' <% if (helpMessage && helpMessage.length) { %>',
|
|
|
|
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
|
|
|
|
' <% } %>',
|
|
|
|
'</div>'
|
|
|
|
].join("\n")),
|
|
|
|
events: {
|
|
|
|
"change input": "checkNumeric",
|
|
|
|
"focus input": "clearInvalid"
|
|
|
|
},
|
|
|
|
checkNumeric: function(e) {
|
|
|
|
var field = _.defaults(this.field.toJSON(), this.defaults),
|
|
|
|
attrArr = this.field.get("name").split('.'),
|
|
|
|
name = attrArr.shift(),
|
|
|
|
value = this.getValueFromDOM(),
|
|
|
|
min_value = field.min,
|
|
|
|
max_value = field.max,
|
|
|
|
isValid = true,
|
|
|
|
intPattern = new RegExp("^-?[0-9]+(\.?[0-9]*)?$"),
|
|
|
|
isMatched = intPattern.test(value);
|
|
|
|
|
|
|
|
// Below logic will validate input
|
|
|
|
if (!isMatched) {
|
|
|
|
isValid = false;
|
|
|
|
this.model.errorModel.unset(name);
|
|
|
|
this.model.errorModel.set(
|
|
|
|
name,
|
|
|
|
S(pgAdmin.Browser.messages.MUST_BE_NUM).sprintf(
|
|
|
|
field.label
|
|
|
|
).value()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Below will check if entered value is in-between min & max range
|
|
|
|
if (isValid && (!_.isUndefined(min_value) && value < min_value)) {
|
|
|
|
isValid = false;
|
|
|
|
this.model.errorModel.unset(name);
|
|
|
|
this.model.errorModel.set(
|
|
|
|
name,
|
|
|
|
S(pgAdmin.Browser.messages.MUST_GR_EQ).sprintf(
|
|
|
|
field.label,
|
|
|
|
min_value
|
|
|
|
).value()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isValid && (!_.isUndefined(max_value) && value > max_value)) {
|
|
|
|
isValid = false;
|
|
|
|
this.model.errorModel.unset(name);
|
|
|
|
this.model.errorModel.set(
|
|
|
|
name,
|
|
|
|
S(pgAdmin.Browser.messages.MUST_LESS_EQ).sprintf(
|
|
|
|
field.label,
|
|
|
|
max_value
|
|
|
|
).value()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// After validation we need to set that value into model (only if all flags are true)
|
|
|
|
if (isValid) {
|
|
|
|
this.stopListening(this.model, "change:" + name, this.render);
|
|
|
|
this.model.set(name, value);
|
|
|
|
this.listenTo(this.model, "change:" + name, this.render);
|
|
|
|
} else {
|
|
|
|
if (this.model.collection || this.model.handler) {
|
|
|
|
(this.model.collection || this.model.handler).trigger(
|
|
|
|
'pgadmin-session:model:invalid', this.model.errorModel.get(name), this.model
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
(this.model).trigger(
|
|
|
|
'pgadmin-session:invalid', this.model.errorModel.get(name), this.model
|
|
|
|
);
|
|
|
|
}
|
2016-01-07 13:20:11 +00:00
|
|
|
}
|
2016-01-05 11:19:58 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2015-10-28 17:06:09 +00:00
|
|
|
///////
|
|
|
|
// Generate a schema (as group members) based on the model's schema
|
|
|
|
//
|
|
|
|
// It will be used by the grid, properties, and dialog view generation
|
|
|
|
// functions.
|
2016-01-17 16:51:02 +00:00
|
|
|
var generateViewSchema = Backform.generateViewSchema = function(
|
|
|
|
node_info, Model, mode, node, treeData, noSQL, subschema
|
|
|
|
) {
|
2015-10-28 17:06:09 +00:00
|
|
|
var proto = (Model && Model.prototype) || Model,
|
2016-01-17 16:51:02 +00:00
|
|
|
schema = subschema || (proto && proto.schema),
|
2016-03-06 13:20:05 +00:00
|
|
|
pgBrowser = window.pgAdmin.Browser, fields = [],
|
|
|
|
groupInfo = {};
|
2015-10-28 17:06:09 +00:00
|
|
|
|
|
|
|
// 'schema' has the information about how to generate the form.
|
|
|
|
if (schema && _.isArray(schema)) {
|
|
|
|
var evalASFunc = evalASFunc = function(prop) {
|
|
|
|
return ((prop && proto[prop] &&
|
|
|
|
typeof proto[prop] == "function") ? proto[prop] : prop);
|
|
|
|
};
|
2016-01-17 16:51:02 +00:00
|
|
|
var groups = {},
|
|
|
|
server_info = node_info && ('server' in node_info) &&
|
|
|
|
pgBrowser.serverInfo && pgBrowser.serverInfo[node_info.server._id];
|
2015-10-28 17:06:09 +00:00
|
|
|
|
|
|
|
_.each(schema, function(s) {
|
|
|
|
// Do we understand - what control, we're creating
|
|
|
|
// here?
|
2016-03-06 13:20:05 +00:00
|
|
|
if (s.type == 'group') {
|
|
|
|
var ver_in_limit = (_.isUndefined(server_info) ? true :
|
|
|
|
((_.isUndefined(s.server_type) ? true :
|
|
|
|
(server_info.type in s.server_type)) &&
|
|
|
|
(_.isUndefined(s.min_version) ? true :
|
|
|
|
(server_info.version >= s.min_version)) &&
|
|
|
|
(_.isUndefined(s.max_version) ? true :
|
2016-05-20 12:13:30 +00:00
|
|
|
(server_info.version <= s.max_version)))),
|
|
|
|
visible = true;
|
|
|
|
|
|
|
|
if (s.mode && _.isObject(s.mode))
|
|
|
|
visible = (_.indexOf(s.mode, mode) > -1);
|
|
|
|
if (visible)
|
|
|
|
visible = evalASFunc(s.visible);
|
|
|
|
|
2016-03-06 13:20:05 +00:00
|
|
|
groupInfo[s.id] = {
|
|
|
|
label: s.label || s.id,
|
|
|
|
version_compatible: ver_in_limit,
|
2016-05-20 12:13:30 +00:00
|
|
|
visible: visible
|
2016-03-06 13:20:05 +00:00
|
|
|
};
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-10-28 17:06:09 +00:00
|
|
|
if (!s.mode || (s && s.mode && _.isObject(s.mode) &&
|
|
|
|
_.indexOf(s.mode, mode) != -1)) {
|
|
|
|
// Each field is kept in specified group, or in
|
|
|
|
// 'General' category.
|
2016-04-12 16:00:12 +00:00
|
|
|
var group = s.group || pgBrowser.messages.GENERAL_CATEGORY,
|
2015-12-16 08:13:04 +00:00
|
|
|
control = s.control || Backform.getMappedControl(s.type, mode),
|
2015-10-28 17:06:09 +00:00
|
|
|
cell = s.cell || Backform.getMappedControl(s.type, 'cell');
|
|
|
|
|
|
|
|
if (control == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate the empty group list (if not exists)
|
|
|
|
groups[group] = (groups[group] || []);
|
2015-12-16 10:32:12 +00:00
|
|
|
var ver_in_limit = (_.isUndefined(server_info) ? true :
|
|
|
|
((_.isUndefined(s.server_type) ? true :
|
|
|
|
(server_info.type in s.server_type)) &&
|
|
|
|
(_.isUndefined(s.min_version) ? true :
|
|
|
|
(server_info.version >= s.min_version)) &&
|
|
|
|
(_.isUndefined(s.max_version) ? true :
|
|
|
|
(server_info.version <= s.max_version)))),
|
2015-12-26 10:45:12 +00:00
|
|
|
disabled = ((mode == 'properties') || !ver_in_limit),
|
|
|
|
schema_node = (s.node && _.isString(s.node) &&
|
|
|
|
s.node in pgBrowser.Nodes && pgBrowser.Nodes[s.node]) || node;
|
2015-10-28 17:06:09 +00:00
|
|
|
|
|
|
|
var o = _.extend(_.clone(s), {
|
|
|
|
name: s.id,
|
|
|
|
// This can be disabled in some cases (if not hidden)
|
2015-11-19 17:45:48 +00:00
|
|
|
|
|
|
|
disabled: (disabled ? true : evalASFunc(s.disabled)),
|
2016-03-03 14:03:59 +00:00
|
|
|
editable: _.isUndefined(s.editable) ?
|
|
|
|
pgAdmin.editableCell : evalASFunc(s.editable),
|
2015-11-19 17:45:48 +00:00
|
|
|
subnode: ((_.isString(s.model) && s.model in pgBrowser.Nodes) ?
|
|
|
|
pgBrowser.Nodes[s.model].model : s.model),
|
|
|
|
canAdd: (disabled ? false : evalASFunc(s.canAdd)),
|
2016-04-13 14:54:17 +00:00
|
|
|
canAddRow: (disabled ? false : evalASFunc(s.canAddRow)),
|
2015-11-19 17:45:48 +00:00
|
|
|
canEdit: (disabled ? false : evalASFunc(s.canEdit)),
|
|
|
|
canDelete: (disabled ? false : evalASFunc(s.canDelete)),
|
2016-04-13 14:54:17 +00:00
|
|
|
canEditRow: (disabled ? false : evalASFunc(s.canEditRow)),
|
|
|
|
canDeleteRow: (disabled ? false : evalASFunc(s.canDeleteRow)),
|
2016-02-01 11:07:05 +00:00
|
|
|
transform: evalASFunc(s.transform),
|
2015-10-28 17:06:09 +00:00
|
|
|
mode: mode,
|
|
|
|
control: control,
|
2015-11-19 17:45:48 +00:00
|
|
|
cell: cell,
|
|
|
|
node_info: node_info,
|
2015-12-26 10:45:12 +00:00
|
|
|
schema_node: schema_node,
|
2016-01-15 13:55:45 +00:00
|
|
|
// Do we need to show this control in this mode?
|
|
|
|
visible: evalASFunc(s.visible),
|
2016-01-09 12:29:56 +00:00
|
|
|
node: node,
|
2016-01-12 06:31:45 +00:00
|
|
|
node_data: treeData,
|
2016-02-10 06:42:06 +00:00
|
|
|
version_compatible: ver_in_limit
|
2015-10-28 17:06:09 +00:00
|
|
|
});
|
|
|
|
delete o.id;
|
|
|
|
|
2016-01-09 12:29:56 +00:00
|
|
|
// Temporarily store in dictionary format for
|
2015-10-28 17:06:09 +00:00
|
|
|
// utilizing it later.
|
|
|
|
groups[group].push(o);
|
2016-01-17 16:51:02 +00:00
|
|
|
|
2016-01-19 12:23:33 +00:00
|
|
|
if (s.type == 'nested') {
|
2016-01-17 16:51:02 +00:00
|
|
|
delete o.name;
|
|
|
|
delete o.cell;
|
|
|
|
|
|
|
|
o.schema = Backform.generateViewSchema(
|
|
|
|
node_info, Model, mode, node, treeData, true, s.schema
|
|
|
|
);
|
|
|
|
o.control = o.control || 'tab';
|
|
|
|
}
|
2015-10-28 17:06:09 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Do we have fields to genreate controls, which we
|
|
|
|
// understand?
|
|
|
|
if (_.isEmpty(groups)) {
|
|
|
|
return null;
|
|
|
|
}
|
2016-01-17 16:51:02 +00:00
|
|
|
|
|
|
|
if (!noSQL && node && node.hasSQL && (mode == 'create' || mode == 'edit')) {
|
2015-12-26 10:45:12 +00:00
|
|
|
groups[pgBrowser.messages.SQL_TAB] = [{
|
|
|
|
name: 'sql',
|
|
|
|
visible: true,
|
|
|
|
disabled: false,
|
|
|
|
type: 'text',
|
|
|
|
control: 'sql-tab',
|
|
|
|
node_info: node_info,
|
|
|
|
schema_node: node,
|
|
|
|
node_data: treeData
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2016-01-17 16:51:02 +00:00
|
|
|
// Create an array from the dictionary with proper required
|
|
|
|
// structure.
|
|
|
|
_.each(groups, function(val, key) {
|
2016-05-20 12:13:30 +00:00
|
|
|
fields.push(
|
|
|
|
_.extend(
|
|
|
|
_.defaults(
|
|
|
|
groupInfo[key] || {label: key},
|
|
|
|
{version_compatible: true, visible: true}
|
|
|
|
), {fields: val})
|
|
|
|
);
|
2016-01-17 16:51:02 +00:00
|
|
|
});
|
2015-10-28 17:06:09 +00:00
|
|
|
}
|
2016-01-17 16:51:02 +00:00
|
|
|
|
|
|
|
return fields;
|
2016-01-09 12:29:56 +00:00
|
|
|
};
|
2015-10-28 17:06:09 +00:00
|
|
|
|
2016-05-21 10:11:35 +00:00
|
|
|
var Select2Formatter = function() {};
|
|
|
|
_.extend(Select2Formatter.prototype, {
|
|
|
|
fromRaw: function(rawData, model) {
|
|
|
|
return JSON.stringify(_.escape(rawData));
|
|
|
|
},
|
|
|
|
toRaw: function(formattedData, model) {
|
|
|
|
return formattedData;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-01-09 12:38:44 +00:00
|
|
|
/*
|
|
|
|
* Backform Select2 control.
|
|
|
|
*/
|
|
|
|
var Select2Control = Backform.Select2Control = Backform.SelectControl.extend({
|
2016-05-21 10:11:35 +00:00
|
|
|
defaults: _.extend({}, Backform.SelectControl.prototype.defaults, {
|
|
|
|
select2: {
|
|
|
|
first_empty: true,
|
|
|
|
multiple: false
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
formatter: Select2Formatter,
|
|
|
|
template: _.template([
|
|
|
|
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
|
|
|
|
'<div class="<%=Backform.controlsClassName%>">',
|
|
|
|
' <select class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>"',
|
|
|
|
' name="<%=name%>" value="<%-value%>" <%=disabled ? "disabled" : ""%>',
|
|
|
|
' <%=required ? "required" : ""%><%= select2.multiple ? " multiple>" : ">" %>',
|
|
|
|
' <%=select2.first_empty ? " <option></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) %>',
|
|
|
|
' <% if (!select2.multiple && option.value === rawValue) {%>selected="selected"<%}%>',
|
|
|
|
' <% if (select2.multiple && rawValue && rawValue.indexOf(option.value) != -1){%>selected="selected" data-index="rawValue.indexOf(option.value)"<%}%>',
|
|
|
|
' <%= disabled ? "disabled" : ""%>><%-option.label%></option>',
|
|
|
|
' <%}%>',
|
|
|
|
' </select>',
|
|
|
|
' <% if (helpMessage && helpMessage.length) { %>',
|
|
|
|
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
|
|
|
|
' <% } %>',
|
|
|
|
'</div>'
|
|
|
|
].join("\n")),
|
2016-01-09 12:38:44 +00:00
|
|
|
render: function() {
|
2016-05-21 10:11:35 +00:00
|
|
|
|
2016-05-12 10:44:01 +00:00
|
|
|
if(this.$sel && this.$sel.select2) {
|
|
|
|
this.$sel.select2('destroy')
|
|
|
|
}
|
2016-01-09 12:38:44 +00:00
|
|
|
|
2016-05-21 10:11:35 +00:00
|
|
|
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);
|
|
|
|
};
|
|
|
|
|
|
|
|
data.select2 = data.select2 || {};
|
|
|
|
_.defaults(data.select2, this.defaults.select2, {
|
|
|
|
first_empty: true,
|
|
|
|
multiple: false
|
|
|
|
});
|
|
|
|
|
|
|
|
// Evaluate the disabled, visible, and required option
|
|
|
|
_.extend(data, {
|
|
|
|
disabled: evalF(data.disabled, data, this.model),
|
|
|
|
visible: evalF(data.visible, data, this.model),
|
|
|
|
required: evalF(data.required, data, this.model)
|
|
|
|
});
|
|
|
|
|
|
|
|
// Evaluation the options
|
|
|
|
if (_.isFunction(data.options)) {
|
|
|
|
try {
|
|
|
|
data.options = data.options(this)
|
|
|
|
} catch(e) {
|
|
|
|
// Do nothing
|
|
|
|
data.options = []
|
|
|
|
this.model.trigger(
|
|
|
|
'pgadmin-view:transform:error', this.model, this.field, e
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clean up first
|
|
|
|
this.$el.removeClass(Backform.hiddenClassname);
|
|
|
|
|
|
|
|
if (!data.visible)
|
|
|
|
this.$el.addClass(Backform.hiddenClassname);
|
|
|
|
|
|
|
|
this.$el.html(this.template(data)).addClass(field.name);
|
|
|
|
|
|
|
|
var select2Opts = _.extend({
|
|
|
|
disabled: data.disabled
|
|
|
|
}, field.select2, {
|
|
|
|
options: (this.field.get('options') || this.defaults.options)
|
|
|
|
});
|
Improvised the 'transform/options' function usage with the Select2Cell.
The current implementaton binds the cell/control object, and the ajax
data in the asychronous Cells/Controls with the 'options' functions
extended from the Select2Cell.
The problem starts when we try to fetch the current model from that
options/transform/filter function to do some operation, which does not
require in most of the cases. Except the privileges control - where we
needed the current model for omitting the existing selected object
during transformation, and filtering.
In order resolved the issue, we need a common object, which is shared
among the Cell. In backgrid, the 'Column' object is mong the cell,
hence - implementation logic has been changed to bid the 'Column' object
with the 'options' function and, passed the 'Cell' object as an
arguments.
Because - we do use the common function 'transform' between 'Control'
and 'Cell', we needed make changes in the Select2Control to pass the
Control object as an arguments.
And, make the changes in the privileges control to use the new
implementation. The same logic is also required in some of the
operations, we will be/are working on the table/column nodes.
2016-04-08 05:30:48 +00:00
|
|
|
|
2016-01-09 12:38:44 +00:00
|
|
|
/*
|
|
|
|
* Add empty option as Select2 requires any empty '<option><option>' for
|
|
|
|
* some of its functionality to work and initialize select2 control.
|
|
|
|
*/
|
2016-05-21 10:11:35 +00:00
|
|
|
this.$sel = this.$el.find("select").select2(select2Opts);
|
|
|
|
|
|
|
|
this.updateInvalid();
|
Improvised the 'transform/options' function usage with the Select2Cell.
The current implementaton binds the cell/control object, and the ajax
data in the asychronous Cells/Controls with the 'options' functions
extended from the Select2Cell.
The problem starts when we try to fetch the current model from that
options/transform/filter function to do some operation, which does not
require in most of the cases. Except the privileges control - where we
needed the current model for omitting the existing selected object
during transformation, and filtering.
In order resolved the issue, we need a common object, which is shared
among the Cell. In backgrid, the 'Column' object is mong the cell,
hence - implementation logic has been changed to bid the 'Column' object
with the 'options' function and, passed the 'Cell' object as an
arguments.
Because - we do use the common function 'transform' between 'Control'
and 'Cell', we needed make changes in the Select2Control to pass the
Control object as an arguments.
And, make the changes in the privileges control to use the new
implementation. The same logic is also required in some of the
operations, we will be/are working on the table/column nodes.
2016-04-08 05:30:48 +00:00
|
|
|
|
2016-01-09 12:38:44 +00:00
|
|
|
return this;
|
2016-05-21 10:11:35 +00:00
|
|
|
},
|
|
|
|
getValueFromDOM: function() {
|
|
|
|
return Backform.SelectControl.prototype.getValueFromDOM.apply(
|
|
|
|
this, arguments
|
|
|
|
);
|
2016-01-09 12:38:44 +00:00
|
|
|
}
|
|
|
|
});
|
2016-01-17 16:51:02 +00:00
|
|
|
|
|
|
|
var FieldsetControl = Backform.FieldsetControl = Backform.Fieldset.extend({
|
|
|
|
initialize: function(opts) {
|
2016-03-07 10:35:01 +00:00
|
|
|
Backform.Control.prototype.initialize.apply(
|
|
|
|
this, arguments
|
|
|
|
);
|
2016-01-17 16:51:02 +00:00
|
|
|
Backform.Dialog.prototype.initialize.apply(
|
|
|
|
this, [{schema: opts.field.get('schema')}]
|
|
|
|
);
|
|
|
|
this.dialog = opts.dialog;
|
|
|
|
this.tabIndex = opts.tabIndex;
|
2016-03-07 10:35:01 +00:00
|
|
|
|
|
|
|
// Listen to the dependent fields in the model for any change
|
|
|
|
var deps = this.field.get('deps');
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
if (deps && _.isArray(deps)) {
|
|
|
|
_.each(deps, function(d) {
|
|
|
|
attrArr = d.split('.');
|
|
|
|
name = attrArr.shift();
|
|
|
|
self.listenTo(self.model, "change:" + name, self.render);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
// Render using Backform.Fieldset (only if this control is visible)
|
|
|
|
orig_render: Backform.Fieldset.prototype.render,
|
|
|
|
render: function() {
|
|
|
|
var field = _.defaults(this.field.toJSON(), this.defaults),
|
|
|
|
evalF = function(f, d, m) {
|
|
|
|
return (_.isFunction(f) ? !!f.apply(d, [m]) : !!f);
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!field.version_compatible ||
|
|
|
|
!evalF(field.visible, field, this.model)) {
|
|
|
|
this.cleanup();
|
|
|
|
this.$el.empty()
|
|
|
|
} else {
|
|
|
|
this.orig_render.apply(this, arguments);
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
formatter: function() {},
|
|
|
|
cleanup: function() {
|
|
|
|
Backform.Fieldset.prototype.cleanup.apply(this);
|
|
|
|
},
|
|
|
|
remove: function() {
|
|
|
|
Backform.Control.prototype.remove.apply(this, arguments);
|
|
|
|
Backform.Dialog.prototype.remove.apply(this, arguments);
|
2016-01-17 16:51:02 +00:00
|
|
|
},
|
|
|
|
className: function() {
|
|
|
|
return 'set-group';
|
|
|
|
},
|
|
|
|
tabPanelClassName: function() {
|
|
|
|
return Backform.tabClassName;
|
|
|
|
},
|
|
|
|
fieldsetClass: 'inline-fieldset',
|
|
|
|
legendClass: '',
|
|
|
|
contentClass: '',
|
|
|
|
collapse: false
|
|
|
|
});
|
|
|
|
|
2016-03-07 10:35:01 +00:00
|
|
|
|
|
|
|
// Backform Tab Control (in bootstrap tabbular)
|
|
|
|
// A collection of field models.
|
|
|
|
var TabControl = Backform.TabControl = Backform.FieldsetControl.extend({
|
|
|
|
tagName: "div",
|
|
|
|
className: 'inline-tab-panel',
|
|
|
|
tabPanelClassName: 'inline-tab-panel',
|
|
|
|
initialize: function(opts) {
|
|
|
|
Backform.FieldsetControl.prototype.initialize.apply(
|
|
|
|
this, arguments
|
|
|
|
);
|
|
|
|
this.tabIndex = (opts.tabIndex || parseInt(Math.random() * 1000)) + 1;
|
|
|
|
},
|
|
|
|
// Render using Backform.Dialog (tabular UI) (only if this control is
|
|
|
|
// visible).
|
|
|
|
orig_render: Backform.Dialog.prototype.render,
|
|
|
|
template: Backform.Dialog.prototype.template
|
|
|
|
});
|
|
|
|
|
2016-03-18 16:54:43 +00:00
|
|
|
|
|
|
|
// Backform Tab Control (in bootstrap tabbular)
|
|
|
|
// A collection of field models.
|
|
|
|
var PlainFieldsetControl = Backform.PlainFieldsetControl = Backform.FieldsetControl.extend({
|
|
|
|
initialize: function(opts) {
|
|
|
|
Backform.FieldsetControl.prototype.initialize.apply(
|
|
|
|
this, arguments
|
|
|
|
);
|
|
|
|
},
|
|
|
|
template: {
|
|
|
|
'header': _.template([
|
|
|
|
'<fieldset class="<%=fieldsetClass%>" <%=disabled ? "disabled" : ""%>>',
|
|
|
|
' <% if (legend != false) { %>',
|
|
|
|
' <legend class="<%=legendClass%>" <%=collapse ? "data-toggle=\'collapse\'" : ""%> data-target="#<%=cId%>"><%=collapse ? "<span class=\'caret\'></span>" : "" %></legend>',
|
|
|
|
' <% } %>',
|
|
|
|
'</fieldset>'
|
|
|
|
].join("\n")),
|
|
|
|
'content': _.template(
|
|
|
|
' <div id="<%= cId %>" class="<%=contentClass%>"></div>'
|
|
|
|
)},
|
|
|
|
fieldsetClass: 'inline-fieldset-without-border',
|
|
|
|
legend: false,
|
|
|
|
});
|
|
|
|
|
2016-02-05 08:06:57 +00:00
|
|
|
/*
|
|
|
|
* Control For Code Mirror SQL text area.
|
|
|
|
*/
|
|
|
|
var SqlFieldControl = Backform.SqlFieldControl = Backform.TextareaControl.extend({
|
|
|
|
|
|
|
|
defaults: {
|
|
|
|
label: "",
|
2016-04-29 10:35:26 +00:00
|
|
|
extraClasses: [], // Add default control height
|
2016-02-05 08:06:57 +00:00
|
|
|
helpMessage: null,
|
2016-03-11 09:24:05 +00:00
|
|
|
maxlength: 4096,
|
|
|
|
rows: undefined
|
2016-02-05 08:06:57 +00:00
|
|
|
},
|
|
|
|
|
2016-03-22 16:36:54 +00:00
|
|
|
// Customize template to add new styles
|
|
|
|
template: _.template([
|
|
|
|
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
|
|
|
|
'<div class="<%=Backform.controlsClassName%> sql_field_layout <%=extraClasses.join(\' \')%>">',
|
|
|
|
' <textarea ',
|
|
|
|
' class="<%=Backform.controlClassName%> " name="<%=name%>"',
|
|
|
|
' maxlength="<%=maxlength%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%>',
|
|
|
|
' rows=<%=rows%>',
|
|
|
|
' <%=required ? "required" : ""%>><%-value%></textarea>',
|
|
|
|
' <% if (helpMessage && helpMessage.length) { %>',
|
|
|
|
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
|
|
|
|
' <% } %>',
|
|
|
|
'</div>'
|
|
|
|
].join("\n")),
|
|
|
|
|
2016-02-05 08:06:57 +00:00
|
|
|
/*
|
|
|
|
* Initialize the SQL Field control properly
|
|
|
|
*/
|
|
|
|
initialize: function(o) {
|
|
|
|
Backform.TextareaControl.prototype.initialize.apply(this, arguments);
|
2016-04-22 09:54:18 +00:00
|
|
|
this.sqlCtrl = null;
|
2016-02-05 08:06:57 +00:00
|
|
|
|
|
|
|
// There is an issue with the Code Mirror SQL.
|
|
|
|
//
|
|
|
|
// It does not initialize the code mirror object completely when the
|
|
|
|
// referenced textarea is hidden (not visible), hence - we need to
|
|
|
|
// refresh the code mirror object on 'pg-property-tab-changed' event to
|
|
|
|
// make it work properly.
|
|
|
|
this.listenTo(this.model, 'pg-property-tab-changed', this.refreshTextArea);
|
|
|
|
},
|
|
|
|
|
|
|
|
getValueFromDOM: function() {
|
2016-04-22 09:54:18 +00:00
|
|
|
return this.sqlCtrl.getValue();
|
2016-02-05 08:06:57 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
render: function() {
|
2016-04-22 09:54:18 +00:00
|
|
|
// Clean up the existing sql control
|
|
|
|
if (this.sqlCtrl) {
|
|
|
|
delete this.sqlCtrl;
|
|
|
|
this.sqlCtrl = null;
|
|
|
|
this.$el.empty();
|
|
|
|
}
|
|
|
|
|
2016-02-05 08:06:57 +00:00
|
|
|
// Use the Backform TextareaControl's render function
|
|
|
|
Backform.TextareaControl.prototype.render.apply(this, arguments);
|
2016-03-04 11:00:17 +00:00
|
|
|
|
|
|
|
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 option
|
2016-04-22 09:54:18 +00:00
|
|
|
var isDisabled = evalF(data.disabled, data, this.model),
|
2016-04-22 17:25:50 +00:00
|
|
|
isVisible = evalF(data.visible, data, this.model),
|
|
|
|
self = this;
|
2016-03-04 11:00:17 +00:00
|
|
|
|
2016-04-22 17:25:50 +00:00
|
|
|
self.sqlCtrl = CodeMirror.fromTextArea(
|
2016-02-05 08:06:57 +00:00
|
|
|
(self.$el.find("textarea")[0]), {
|
|
|
|
lineNumbers: true,
|
|
|
|
mode: "text/x-sql",
|
2016-03-04 11:00:17 +00:00
|
|
|
readOnly: isDisabled
|
2016-02-05 08:06:57 +00:00
|
|
|
});
|
|
|
|
|
2016-03-04 11:00:17 +00:00
|
|
|
if (!isVisible)
|
2016-04-22 17:25:50 +00:00
|
|
|
self.$el.addClass(Backform.hiddenClassname);
|
2016-03-04 11:00:17 +00:00
|
|
|
|
2016-04-22 09:54:18 +00:00
|
|
|
var self = this;
|
2016-02-05 08:06:57 +00:00
|
|
|
// Refresh SQL Field to refresh the control lazily after it renders
|
|
|
|
setTimeout(function() {
|
|
|
|
self.refreshTextArea.apply(self);
|
2016-04-29 10:11:24 +00:00
|
|
|
}, 0);
|
2016-02-05 08:06:57 +00:00
|
|
|
|
|
|
|
return self;
|
|
|
|
},
|
|
|
|
|
|
|
|
refreshTextArea: function() {
|
2016-04-29 10:11:24 +00:00
|
|
|
if (this.sqlCtrl) {
|
|
|
|
this.sqlCtrl.refresh();
|
|
|
|
}
|
2016-02-05 08:06:57 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
remove: function() {
|
2016-04-22 09:54:18 +00:00
|
|
|
// Clean up the sql control
|
|
|
|
if (this.sqlCtrl) {
|
|
|
|
delete this.sqlCtrl;
|
|
|
|
this.sqlCtrl = null;
|
2016-04-22 17:25:50 +00:00
|
|
|
this.$el.empty();
|
2016-04-22 09:54:18 +00:00
|
|
|
}
|
|
|
|
|
2016-02-05 08:06:57 +00:00
|
|
|
this.stopListening(this.model, "pg-property-tab-changed", this.refreshTextArea);
|
|
|
|
|
|
|
|
Backform.TextareaControl.prototype.remove.apply(this, arguments);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-04-28 06:22:04 +00:00
|
|
|
// We will use this control just as a annotate in Backform
|
|
|
|
var NoteControl = Backform.NoteControl = Backform.Control.extend({
|
|
|
|
defaults: {
|
|
|
|
label: window.pgAdmin.Browser.messages.NOTE_CTRL_LABEL,
|
|
|
|
text: '',
|
|
|
|
extraClasses: [],
|
|
|
|
noteClass: 'backform_control_notes'
|
|
|
|
},
|
|
|
|
template: _.template([
|
|
|
|
'<div class="<%=noteClass%> col-xs-12 <%=extraClasses.join(\' \')%>">',
|
|
|
|
'<label class="control-label"><%=label%>:</label>',
|
|
|
|
'<span><%=text%></span></div>'
|
|
|
|
].join("\n"))
|
|
|
|
});
|
|
|
|
|
2016-05-12 18:34:28 +00:00
|
|
|
/*
|
|
|
|
* Input File Control: This control is used with Storage Manager Dialog,
|
|
|
|
* It allows user to perform following operations:
|
|
|
|
* - Select File
|
|
|
|
* - Select Folder
|
|
|
|
* - Create File
|
|
|
|
* - Opening Storage Manager Dialog itself.
|
|
|
|
*/
|
|
|
|
var FileControl = Backform.FileControl = Backform.InputControl.extend({
|
|
|
|
defaults: {
|
|
|
|
type: "text",
|
|
|
|
label: "",
|
|
|
|
min: undefined,
|
|
|
|
max: undefined,
|
|
|
|
maxlength: 255,
|
|
|
|
extraClasses: [],
|
|
|
|
dialog_title: '',
|
|
|
|
btn_primary: '',
|
|
|
|
helpMessage: null,
|
|
|
|
dialog_type: 'select_file'
|
|
|
|
},
|
|
|
|
initialize: function(){
|
|
|
|
Backform.InputControl.prototype.initialize.apply(this, arguments);
|
|
|
|
|
|
|
|
// Listen click events of Storage Manager dialog buttons
|
|
|
|
pgAdmin.Browser.Events.on('pgadmin-storage:finish_btn:'+this.field.get('dialog_type'), this.storage_dlg_hander, this);
|
|
|
|
},
|
|
|
|
template: _.template([
|
|
|
|
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
|
|
|
|
'<div class="<%=Backform.controlsClassName%>">',
|
|
|
|
'<div class="file_selection_ctrl form-control">',
|
|
|
|
'<input type="<%=type%>" class="browse_file_input form-control <%=extraClasses.join(\' \')%>" name="<%=name%>" min="<%=min%>" max="<%=max%>"maxlength="<%=maxlength%>" value="<%-value%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> />',
|
|
|
|
'<button class="btn fa fa-ellipsis-h select_item pull-right" <%=disabled ? "disabled" : ""%> ></button>',
|
|
|
|
'<% if (helpMessage && helpMessage.length) { %>',
|
|
|
|
'<span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
|
|
|
|
'<% } %>',
|
|
|
|
'</div>',
|
|
|
|
'</div>'
|
|
|
|
].join("\n")),
|
2016-05-20 12:13:30 +00:00
|
|
|
events: function() {
|
2016-05-21 09:31:47 +00:00
|
|
|
// Inherit all default events of InputControl
|
2016-05-20 12:13:30 +00:00
|
|
|
return _.extend({}, Backform.InputControl.prototype.events, {
|
2016-05-21 09:31:47 +00:00
|
|
|
"click .select_item": "onSelect"
|
|
|
|
});
|
2016-05-12 18:34:28 +00:00
|
|
|
},
|
2016-05-21 09:31:47 +00:00
|
|
|
onSelect: function(ev) {
|
2016-05-12 18:34:28 +00:00
|
|
|
var dialog_type = this.field.get('dialog_type');
|
|
|
|
supp_types = this.field.get('supp_types'),
|
|
|
|
btn_primary = this.field.get('btn_primary'),
|
2016-05-21 09:31:47 +00:00
|
|
|
dialog_title = this.field.get('dialog_title'),
|
|
|
|
params = {
|
|
|
|
supported_types: supp_types,
|
|
|
|
dialog_type: dialog_type,
|
|
|
|
dialog_title: dialog_title,
|
|
|
|
btn_primary: btn_primary
|
|
|
|
};
|
|
|
|
|
2016-05-12 18:34:28 +00:00
|
|
|
pgAdmin.FileManager.init();
|
|
|
|
pgAdmin.FileManager.show_dialog(params);
|
|
|
|
},
|
|
|
|
storage_dlg_hander: function(value) {
|
|
|
|
var field = _.defaults(this.field.toJSON(), this.defaults),
|
|
|
|
attrArr = this.field.get("name").split('.'),
|
|
|
|
name = attrArr.shift();
|
|
|
|
|
|
|
|
// Set selected value into the model
|
|
|
|
this.model.set(name, decodeURI(value));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2015-06-30 05:51:55 +00:00
|
|
|
return Backform;
|
|
|
|
}));
|