2015-06-30 05:51:55 +00:00
|
|
|
/*
|
|
|
|
Backform
|
|
|
|
http://github.com/amiliaapp/backform
|
|
|
|
|
|
|
|
Copyright (c) 2014 Amilia Inc.
|
|
|
|
Written by Martin Drapeau
|
|
|
|
Licensed under the MIT @license
|
|
|
|
*/
|
|
|
|
(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',
|
|
|
|
'backgrid', 'codemirror', 'pgadmin.backgrid', 'codemirror.sql',
|
|
|
|
'select2'
|
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'],
|
|
|
|
'numeric': ['uneditable-input', 'input', 'number'],
|
|
|
|
'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-01-15 13:40:29 +00:00
|
|
|
'select2': 'select2'
|
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-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() {
|
|
|
|
// Listen to the dependent fields in the model for any change
|
|
|
|
var self = this,
|
|
|
|
deps = self.field.get('deps');
|
|
|
|
|
|
|
|
self.stopListening(self.model, "change:" + name, self.render);
|
|
|
|
self.stopListening(self.model.errorModel, "change:" + name, self.updateInvalid);
|
|
|
|
|
|
|
|
if (deps && _.isArray(deps)) {
|
|
|
|
_.each(deps, function(d) {
|
|
|
|
|
|
|
|
attrArr = d.split('.');
|
|
|
|
name = attrArr.shift();
|
|
|
|
|
2016-01-15 11:17:17 +00:00
|
|
|
self.stopListening(self.model, "change:" + name, self.render);
|
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>',
|
|
|
|
'</div>'
|
|
|
|
].join("\n")),
|
|
|
|
|
|
|
|
clearInvalid: function() {
|
|
|
|
this.$el.removeClass(Backform.errorClassName);
|
|
|
|
this.$el.find(".pgadmin-control-error-message").remove();
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
updateInvalid: function() {
|
|
|
|
var self = this,
|
|
|
|
errorModel = this.model.errorModel;
|
|
|
|
|
|
|
|
if (!(errorModel instanceof Backbone.Model)) return this;
|
|
|
|
|
|
|
|
this.clearInvalid();
|
|
|
|
|
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(
|
|
|
|
$("<div></div>").addClass('pgadmin-control-error-message col-xs-12 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.
|
|
|
|
*/
|
|
|
|
Backform.InputControl.prototype.events = {
|
|
|
|
"change input": "onChange",
|
|
|
|
"blur input": "onChange",
|
|
|
|
"focus input": "clearInvalid"
|
|
|
|
};
|
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
Backform.TextareaControl.prototype.events = {
|
|
|
|
"change textarea": "onChange",
|
|
|
|
"keyup textarea": "onChange",
|
|
|
|
"paste textarea": "onChange",
|
|
|
|
"selectionchange textarea": "onChange",
|
|
|
|
"focus textarea": "clearInvalid"
|
|
|
|
};
|
|
|
|
|
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 {
|
|
|
|
data.options = data.options.apply(this)
|
|
|
|
} catch(e) {
|
|
|
|
// Do nothing
|
2016-01-09 12:29:56 +00:00
|
|
|
data.options = []
|
2016-01-04 06:04:40 +00:00
|
|
|
this.model.trigger('pgadmin-view:transform:error', m, self.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);
|
|
|
|
this.updateInvalid();
|
|
|
|
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
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>',
|
|
|
|
' <% } %>',
|
|
|
|
'<% } %>',
|
|
|
|
'</div>'
|
|
|
|
].join("\n")),
|
|
|
|
events: {},
|
|
|
|
getValueFromDOM: function() {
|
|
|
|
return this.formatter.toRaw(this.$el.find("span").text(), this.model);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
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: {
|
|
|
|
onText: 'True',
|
|
|
|
offText: 'False',
|
|
|
|
onColor: 'success',
|
|
|
|
offColor: 'default',
|
|
|
|
size: 'small'
|
|
|
|
},
|
|
|
|
extraClasses: []
|
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>',
|
|
|
|
'</div>'
|
|
|
|
].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() {
|
2015-12-17 13:11:36 +00:00
|
|
|
var field = _.defaults(this.field.toJSON(), this.defaults, {options: $.fn.bootstrapSwitch.defaults}),
|
2015-12-04 10:19:08 +00:00
|
|
|
attributes = this.model.toJSON(),
|
|
|
|
attrArr = field.name.split('.'),
|
|
|
|
name = attrArr.shift(),
|
|
|
|
path = attrArr.join('.'),
|
|
|
|
rawValue = this.keyPathAccessor(attributes[name], path);
|
|
|
|
|
|
|
|
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(
|
|
|
|
_.extend(field.options, {'state': rawValue})
|
|
|
|
);
|
|
|
|
|
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,
|
|
|
|
tagName: "form",
|
|
|
|
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;
|
|
|
|
});
|
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(
|
|
|
|
'<div role="tabpanel" class="tab-pane col-sm-12 col-md-12 col-lg-12 col-xs-12 fade" id="<%=cId%>" aria-labelledby="<%=hId%>"></div>'
|
|
|
|
)},
|
|
|
|
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-01-17 16:51:02 +00:00
|
|
|
idx=(this.tabIndex * 100);
|
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) {
|
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);
|
|
|
|
});
|
2015-12-26 10:45:12 +00:00
|
|
|
idx++;
|
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;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
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" : ""%>>',
|
|
|
|
' <legend class="<%=legendClass%>" <%=collapse ? "data-toggle=\'collapse\'" : ""%> data-target="#<%=cId%>"><%=collapse ? "<span class=\'caret\'></span>" : "" %><%=label%></legend>',
|
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')
|
|
|
|
};
|
2015-06-30 05:51:55 +00:00
|
|
|
|
|
|
|
this.$el.empty();
|
|
|
|
|
|
|
|
_.each(this.schema, function(o) {
|
|
|
|
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,
|
|
|
|
model: m
|
|
|
|
});
|
|
|
|
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) {
|
2015-12-17 13:00:39 +00:00
|
|
|
errorMsg = "Developer: Unique column/s [ "+_.difference(uniqueCol, columns)+" ] not found in collection model [ " + columns +" ]."
|
|
|
|
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-01-15 11:17:17 +00:00
|
|
|
handler: self.model.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-01-12 06:31:45 +00:00
|
|
|
if (this.field.get('version_compitible')) {
|
|
|
|
self.listenTo(collection, "add", self.collectionChanged);
|
|
|
|
self.listenTo(collection, "change", self.collectionChanged);
|
|
|
|
}
|
2015-12-17 13:00:39 +00:00
|
|
|
},
|
2016-01-09 12:29:56 +00:00
|
|
|
remove: function() {
|
|
|
|
var self = this;
|
|
|
|
|
2016-01-12 06:31:45 +00:00
|
|
|
if (this.field.get('version_compitible')) {
|
2016-01-15 11:17:17 +00:00
|
|
|
self.stopListening(self.collection, "add", self.collectionChanged);
|
|
|
|
self.stopListening(self.collection, "change", self.collectionChanged);
|
2016-01-12 06:31:45 +00:00
|
|
|
}
|
2016-01-09 12:29:56 +00:00
|
|
|
|
|
|
|
Backform.Control.prototype.remove.apply(this, arguments);
|
|
|
|
},
|
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() {
|
|
|
|
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, m) {
|
|
|
|
return (_.isFunction(f) ? !!f(m) : !!f);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Evaluate the disabled, visible, required, canAdd, & canDelete option
|
|
|
|
_.extend(data, {
|
2016-01-12 06:31:45 +00:00
|
|
|
disabled: (field.version_compitible &&
|
|
|
|
evalF.apply(this.field, [data.disabled, this.model])
|
|
|
|
),
|
|
|
|
visible: evalF.apply(this.field, [data.visible, this.model]),
|
|
|
|
required: evalF.apply(this.field, [data.required, this.model]),
|
|
|
|
canAdd: (field.version_compitible &&
|
|
|
|
evalF.apply(this.field, [data.canAdd, this.model])
|
|
|
|
),
|
|
|
|
canDelete: evalF.apply(this.field, [data.canDelete, this.model])
|
2015-12-17 13:00:39 +00:00
|
|
|
});
|
2016-01-12 06:31:45 +00:00
|
|
|
_.extend(data, {add_label: "ADD"});
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
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,
|
|
|
|
editable: false, cell_priority: -1
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
var collection = this.model.get(data.name);
|
2016-01-12 06:31:45 +00:00
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
$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();
|
|
|
|
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;
|
2015-12-17 13:00:39 +00:00
|
|
|
}
|
2016-01-09 12:29:56 +00:00
|
|
|
}
|
2015-12-17 13:00:39 +00:00
|
|
|
|
2016-01-09 12:29:56 +00:00
|
|
|
$(grid.body.$el.find($("tr.new"))).removeClass("new")
|
2016-01-15 13:40:29 +00:00
|
|
|
var m = new (data.model) (null, {
|
|
|
|
silent: true,
|
|
|
|
handler: self.model.handler || self.model,
|
|
|
|
top: self.model.top || self.model
|
|
|
|
});
|
2016-01-09 12:29:56 +00:00
|
|
|
collection.add(m);
|
2015-12-17 13:00:39 +00:00
|
|
|
|
2016-01-09 12:29:56 +00:00
|
|
|
var idx = collection.indexOf(m),
|
|
|
|
newRow = grid.body.rows[idx].$el;
|
|
|
|
|
|
|
|
newRow.addClass("new");
|
|
|
|
$(newRow).pgMakeVisible('backform-tab');
|
|
|
|
|
|
|
|
return false;
|
2015-12-17 13:00:39 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return $dialog;
|
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;
|
|
|
|
|
|
|
|
self.$el.addClass(Backform.errorClassName).append(
|
|
|
|
$("<div></div>").addClass('pgadmin-control-error-message col-xs-12 help-block').text(error)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
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
|
|
|
|
}),
|
|
|
|
evalF = function(f, m) {
|
|
|
|
return (_.isFunction(f) ? !!f(m) : !!f);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Evaluate the disabled, visible, required, canAdd, cannEdit & canDelete option
|
|
|
|
_.extend(data, {
|
|
|
|
disabled: evalF(data.disabled, this.model),
|
|
|
|
visible: evalF(data.visible, this.model),
|
|
|
|
required: evalF(data.required, this.model),
|
|
|
|
canAdd: evalF(data.canAdd, this.model),
|
|
|
|
canEdit: evalF(data.canEdit, this.model),
|
|
|
|
canDelete: evalF(data.canDelete, this.model)
|
|
|
|
});
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
self.$el.addClass(Backform.errorClassName).append(
|
|
|
|
$("<div></div>").addClass('pgadmin-control-error-message col-xs-12 help-block').text(error)
|
|
|
|
);
|
|
|
|
},
|
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>" ,
|
|
|
|
" <button class='btn-sm btn-default add'>Add</buttton>",
|
|
|
|
"</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,
|
2015-11-17 06:21:09 +00:00
|
|
|
editable: false, cell_priority: -1
|
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
|
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,
|
2015-11-17 06:21:09 +00:00
|
|
|
cell_priority: -2
|
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,
|
|
|
|
model: data.model,
|
|
|
|
silent: true
|
|
|
|
});
|
|
|
|
self.model.set(data.name, collection, {silent: true});
|
|
|
|
}
|
|
|
|
|
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) {
|
|
|
|
e.preventDefault();
|
|
|
|
grid.insertRow({});
|
2016-01-09 12:29:56 +00:00
|
|
|
var newRow = $(grid.body.rows[collection.length - 1].$el);
|
2015-10-28 17:06:09 +00:00
|
|
|
newRow.attr("class", "new").click(function(e) {
|
|
|
|
$(this).attr("class", "");
|
|
|
|
});
|
2016-01-09 12:29:56 +00:00
|
|
|
$(newRow).pgMakeVisible('backform-tab');
|
2015-10-28 17:06:09 +00:00
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
|
|
|
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: "",
|
|
|
|
controlsClassName: "pgadmin-controls col-sm-12",
|
|
|
|
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() {
|
|
|
|
// Use the Backform Control's render function
|
|
|
|
Backform.Control.prototype.render.apply(this, arguments);
|
|
|
|
|
|
|
|
var sqlTab = CodeMirror.fromTextArea(
|
|
|
|
(this.$el.find("textarea")[0]), {
|
|
|
|
lineNumbers: true,
|
|
|
|
mode: "text/x-sql",
|
|
|
|
readOnly: true
|
|
|
|
});
|
|
|
|
this.sqlTab = sqlTab;
|
|
|
|
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
|
|
|
|
|
|
|
// We will send request to sever only if something is changed in model
|
2016-01-15 11:17:17 +00:00
|
|
|
if(this.model.sessChanged()) {
|
2015-12-26 10:45:12 +00:00
|
|
|
|
|
|
|
var self = this,
|
|
|
|
node = self.field.get('schema_node'),
|
|
|
|
msql_url = node.generate_url.apply(
|
|
|
|
node, [
|
2016-01-06 13:50:49 +00:00
|
|
|
null, 'msql', this.field.get('node_data'), !self.model.isNew(),
|
2015-12-26 10:45:12 +00:00
|
|
|
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,
|
2016-01-07 13:01:08 +00:00
|
|
|
data: self.model.toJSON(true, 'GET'),
|
2016-01-04 20:47:37 +00:00
|
|
|
dataType: "json",
|
|
|
|
contentType: "application/json"
|
2015-12-26 10:45:12 +00:00
|
|
|
}).done(function(res) {
|
|
|
|
self.sqlTab.clearHistory();
|
|
|
|
self.sqlTab.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.sqlTab.clearHistory();
|
|
|
|
this.sqlTab.setValue(window.pgAdmin.Browser.messages.SQL_NO_CHANGE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
remove: function() {
|
|
|
|
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-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),
|
|
|
|
pgBrowser = window.pgAdmin.Browser, fields = [];
|
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?
|
|
|
|
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.
|
|
|
|
var group = s.group || pgBrowser.messages.general_cateogty,
|
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)),
|
2015-12-16 08:13:04 +00:00
|
|
|
editable: (disabled ? false : (_.isUndefined(s.editable) ? pgAdmin.editableCell : !!(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)),
|
|
|
|
canEdit: (disabled ? false : evalASFunc(s.canEdit)),
|
|
|
|
canDelete: (disabled ? false : evalASFunc(s.canDelete)),
|
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,
|
|
|
|
version_compitible: 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) {
|
|
|
|
fields.push({label: key, fields: val});
|
|
|
|
});
|
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-01-09 12:38:44 +00:00
|
|
|
/*
|
|
|
|
* Backform Select2 control.
|
|
|
|
*/
|
|
|
|
var Select2Control = Backform.Select2Control = Backform.SelectControl.extend({
|
|
|
|
render: function() {
|
|
|
|
Backform.SelectControl.prototype.render.apply(this, arguments);
|
|
|
|
|
|
|
|
var col = _.defaults(this.field.toJSON(), this.defaults)
|
|
|
|
/*
|
|
|
|
* Add empty option as Select2 requires any empty '<option><option>' for
|
|
|
|
* some of its functionality to work and initialize select2 control.
|
|
|
|
*/
|
|
|
|
this.$el.find("select").prepend($('<option></option>')).select2(col.select2);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
});
|
2016-01-17 16:51:02 +00:00
|
|
|
|
|
|
|
// Backform Tab Control (in bootstrap tabbular)
|
|
|
|
// A collection of field models.
|
|
|
|
var TabControl = Backform.TabControl = Backform.Dialog.extend({
|
|
|
|
tagName: "div",
|
|
|
|
className: 'inline-tab-panel',
|
|
|
|
tabPanelClassName: 'inline-tab-panel',
|
|
|
|
initialize: function(opts) {
|
|
|
|
Backform.Dialog.prototype.initialize.apply(
|
|
|
|
this, [{schema: opts.field.get('schema')}]
|
|
|
|
);
|
|
|
|
this.dialog = opts.dialog;
|
|
|
|
this.tabIndex = (opts.tabIndex || parseInt(Math.random() * 1000)) + 1;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var FieldsetControl = Backform.FieldsetControl = Backform.Fieldset.extend({
|
|
|
|
initialize: function(opts) {
|
|
|
|
Backform.Dialog.prototype.initialize.apply(
|
|
|
|
this, [{schema: opts.field.get('schema')}]
|
|
|
|
);
|
|
|
|
this.dialog = opts.dialog;
|
|
|
|
this.tabIndex = opts.tabIndex;
|
|
|
|
},
|
|
|
|
className: function() {
|
|
|
|
return 'set-group';
|
|
|
|
},
|
|
|
|
tabPanelClassName: function() {
|
|
|
|
return Backform.tabClassName;
|
|
|
|
},
|
|
|
|
fieldsetClass: 'inline-fieldset',
|
|
|
|
legendClass: '',
|
|
|
|
contentClass: '',
|
|
|
|
collapse: 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: "",
|
|
|
|
extraClasses: [],
|
|
|
|
helpMessage: null,
|
|
|
|
maxlength: 4096
|
|
|
|
},
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize the SQL Field control properly
|
|
|
|
*/
|
|
|
|
initialize: function(o) {
|
|
|
|
Backform.TextareaControl.prototype.initialize.apply(this, arguments);
|
|
|
|
|
|
|
|
// 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() {
|
|
|
|
return this.sqlField.getValue();
|
|
|
|
},
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
// Use the Backform TextareaControl's render function
|
|
|
|
Backform.TextareaControl.prototype.render.apply(this, arguments);
|
|
|
|
var self = this,
|
|
|
|
sqlField = CodeMirror.fromTextArea(
|
|
|
|
(self.$el.find("textarea")[0]), {
|
|
|
|
lineNumbers: true,
|
|
|
|
mode: "text/x-sql",
|
|
|
|
readOnly: false
|
|
|
|
});
|
|
|
|
|
|
|
|
self.sqlField = sqlField;
|
|
|
|
|
|
|
|
// Refresh SQL Field to refresh the control lazily after it renders
|
|
|
|
setTimeout(function() {
|
|
|
|
self.refreshTextArea.apply(self);
|
|
|
|
}, 100);
|
|
|
|
|
|
|
|
return self;
|
|
|
|
},
|
|
|
|
|
|
|
|
refreshTextArea: function() {
|
|
|
|
this.sqlField.refresh();
|
|
|
|
},
|
|
|
|
|
|
|
|
remove: function() {
|
|
|
|
this.stopListening(this.model, "pg-property-tab-changed", this.refreshTextArea);
|
|
|
|
|
|
|
|
Backform.TextareaControl.prototype.remove.apply(this, arguments);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2015-06-30 05:51:55 +00:00
|
|
|
return Backform;
|
|
|
|
}));
|