pgadmin4/web/pgadmin/browser/static/js/datamodel.js

1302 lines
39 KiB
JavaScript
Raw Normal View History

2019-01-02 04:24:12 -06:00
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
2020-01-02 08:43:50 -06:00
// Copyright (C) 2013 - 2020, The pgAdmin Development Team
2019-01-02 04:24:12 -06:00
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
define([
'underscore', 'sources/pgadmin', 'jquery', 'backbone', 'sources/utils',
], function(_, pgAdmin, $, Backbone, pgadminUtils) {
var pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
pgBrowser.DataModel = Backbone.Model.extend({
/*
* Parsing the existing data
*/
on_server: false,
parse: function(res) {
var self = this;
if (res && _.isObject(res) && 'node' in res && res['node']) {
self.tnode = _.extend({}, res.node);
delete res.node;
}
var objectOp = function(schema) {
if (schema && _.isArray(schema)) {
_.each(schema, function(s) {
var obj, val;
switch (s.type) {
case 'collection':
obj = self.get(s.id);
val = res[s.id];
if (_.isArray(val) || _.isObject(val)) {
if (!obj || !(obj instanceof Backbone.Collection)) {
obj = new(pgBrowser.Node.Collection)(val, {
model: ((_.isString(s.model) &&
s.model in pgBrowser.Nodes) ?
pgBrowser.Nodes[s.model].model : s.model),
top: self.top || self,
handler: self,
parse: true,
silent: true,
attrName: s.id,
});
/*
* Nested collection models may or may not have idAttribute.
* So to decide whether model is new or not set 'on_server'
* flag on such models.
*/
self.set(s.id, obj, {
silent: true,
parse: true,
on_server: true,
});
} else {
/*
* Nested collection models may or may not have idAttribute.
* So to decide whether model is new or not set 'on_server'
* flag on such models.
*/
obj.reset(val, {
silent: true,
parse: true,
on_server: true,
});
}
} else {
obj = null;
}
self.set(s.id, obj, {
silent: true,
});
res[s.id] = obj;
break;
case 'model':
obj = self.get(s.id);
val = res[s.id];
if (!_.isUndefined(val) && !_.isNull(val)) {
if (!obj || !(obj instanceof Backbone.Model)) {
if (_.isString(s.model) &&
s.model in pgBrowser.Nodes[s.model]) {
obj = new(pgBrowser.Nodes[s.model].Model)(
obj, {
silent: true,
top: self.top || self,
handler: self,
attrName: s.id,
}
);
} else {
obj = new(s.model)(obj, {
silent: true,
top: self.top || self,
handler: self,
attrName: s.id,
});
}
}
obj.set(val, {
parse: true,
silent: true,
});
} else {
obj = null;
}
res[s.id] = obj;
break;
case 'nested':
objectOp(s.schema);
break;
default:
break;
}
});
}
};
objectOp(self.schema);
return res;
},
isNew: function() {
if (this.has(this.idAttribute)) {
return !this.has(this.idAttribute);
}
return !this.on_server;
},
primary_key: function() {
if (this.keys && _.isArray(this.keys)) {
var res = {},
self = this;
_.each(self.keys, function(k) {
res[k] = self.attributes[k];
});
return JSON.stringify(res);
}
return this.cid;
},
initialize: function(attributes, options) {
var self = this;
self._previous_key_values = {};
if (!_.isUndefined(options) && 'on_server' in options && options.on_server) {
self.on_server = true;
}
Backbone.Model.prototype.initialize.apply(self, arguments);
if (_.isUndefined(options) || _.isNull(options)) {
options = attributes || {};
}
self.sessAttrs = {};
self.fieldData = {};
self.origSessAttrs = {};
self.objects = [];
self.arrays = [];
self.attrName = options.attrName;
self.top = (options.top || self.collection && self.collection.top || self.collection || self);
self.handler = options.handler ||
(self.collection && self.collection.handler);
self.trackChanges = false;
self.errorModel = new Backbone.Model();
self.node_info = options.node_info;
var obj;
var objectOp = function(schema) {
if (schema && _.isArray(schema)) {
_.each(schema, function(s) {
switch (s.type) {
case 'int':
case 'numeric':
self.fieldData[s.id] = {
id: s.id,
label: s.label,
type: s.type,
min: !_.isUndefined(s.min) ? s.min : undefined,
max: !_.isUndefined(s.max) ? s.max : undefined,
};
break;
default:
self.fieldData[s.id] = {
id: s.id,
label: s.label,
type: s.type,
};
}
switch (s.type) {
case 'array':
self.arrays.push(s.id);
break;
case 'collection':
obj = self.get(s.id);
if (!obj || !(obj instanceof pgBrowser.Node.Collection)) {
if (_.isString(s.model) &&
s.model in pgBrowser.Nodes) {
var node = pgBrowser.Nodes[s.model];
obj = new(node.Collection)(obj, {
model: node.model,
top: self.top || self,
handler: self,
attrName: s.id,
});
} else {
obj = new(pgBrowser.Node.Collection)(obj, {
model: s.model,
top: self.top || self,
handler: self,
attrName: s.id,
});
}
}
obj.name = s.id;
self.objects.push(s.id);
self.set(s.id, obj, {
silent: true,
});
break;
case 'model':
obj = self.get(s.id);
if (!obj || !(obj instanceof Backbone.Model)) {
if (_.isString(s.model) &&
s.model in pgBrowser.Nodes[s.model]) {
obj = new(pgBrowser.Nodes[s.model].Model)(
obj, {
top: self.top || self,
handler: self,
attrName: s.id,
}
);
} else {
obj = new(s.model)(
obj, {
top: self.top || self,
handler: self,
attrName: s.id,
});
}
}
obj.name = s.id;
self.objects.push(s.id);
self.set(s.id, obj, {
silent: true,
});
break;
case 'nested':
objectOp(s.schema);
break;
default:
return;
}
});
}
};
objectOp(self.schema);
if (self.handler && self.handler.trackChanges) {
self.startNewSession();
}
if ('keys' in self && _.isArray(self.keys)) {
_.each(self.keys, function(key) {
self.on('change:' + key, function(m) {
self._previous_key_values[key] = m.previous(key);
});
});
}
return self;
},
// Create a reset function, which allow us to remove the nested object.
reset: function(opts) {
var obj,
reindex = !!(opts && opts.reindex);
if (opts && opts.stop)
this.stopSession();
// Let's not touch the child attributes, if reindex is false.
if (!reindex) {
return;
}
for (var id in this.objects) {
obj = this.get(id);
if (obj) {
if (obj instanceof pgBrowser.DataModel) {
obj.reset(opts);
} else if (obj instanceof Backbone.Model) {
obj.clear(opts);
} else if (obj instanceof pgBrowser.DataCollection) {
obj.reset([], opts);
} else if (obj instanceof Backbone.Collection) {
obj.each(function(m) {
if (m instanceof Backbone.DataModel) {
obj.reset();
obj.clear(opts);
}
});
Backbone.Collection.prototype.reset.call(obj, [], opts);
}
}
}
Backbone.Collection.prototype.reset.apply(this, arguments);
},
sessChanged: function() {
var self = this;
return (_.size(self.sessAttrs) > 0 ||
_.some(self.objects, function(k) {
var obj = self.get(k);
if (!(_.isNull(obj) || _.isUndefined(obj))) {
return obj.sessChanged();
}
return false;
}));
},
sessValid: function() {
var self = this;
// Perform default validations.
if ('default_validate' in self && typeof(self.default_validate) == 'function' &&
_.isString(self.default_validate())) {
return false;
}
if ('validate' in self && _.isFunction(self.validate) &&
_.isString(self.validate.apply(self))) {
return false;
}
return true;
},
set: function(key, val, options) {
var opts = _.isObject(key) ? val : options;
this._changing = true;
this._previousAttributes = _.clone(this.attributes);
this.changed = {};
var res = Backbone.Model.prototype.set.call(this, key, val, options);
this._changing = false;
if ((opts && opts.internal) || !this.trackChanges) {
return true;
}
if (key != null && res) {
var attrs = {},
self = this,
msg;
var attrChanged = function(v, k) {
if (k in self.objects) {
return;
}
attrs[k] = v;
const attrsDefined = self.origSessAttrs[k] && v;
/* If the orig value was null and new one is empty string, then its a "no change" */
/* If the orig value and new value are of different datatype but of same value(numeric) "no change" */
if (_.isEqual(self.origSessAttrs[k], v)
|| (self.origSessAttrs[k] === null && v === '')
|| (attrsDefined ? _.isEqual(self.origSessAttrs[k].toString(), v.toString()) : false)) {
delete self.sessAttrs[k];
} else {
self.sessAttrs[k] = v;
}
};
// Handle both `"key", value` and `{key: value}` -style arguments.
if (typeof key === 'object') {
_.each(key, attrChanged);
} else {
attrChanged(val, key);
}
self.trigger('pgadmin-session:set', self, attrs);
if (!options || !options.silent) {
self.trigger('change', self, options);
}
// Perform default validations.
if ('default_validate' in self && typeof(self.default_validate) == 'function') {
msg = self.default_validate();
}
if ('validate' in self && typeof(self['validate']) === 'function') {
if (!msg) {
msg = self.validate(_.keys(attrs));
}
}
/*
* If any parent present, we will need to inform the parent - that
* I have some issues/fixed the issue.
*
* If not parent found, we will raise the issue
*/
if (_.size(self.errorModel.attributes) == 0) {
if (self.collection || self.handler) {
(self.collection || self.handler).trigger(
'pgadmin-session:model:valid', self, (self.collection || self.handler)
);
} else {
self.trigger('pgadmin-session:valid', self.sessChanged(), self);
}
} else {
msg = msg || _.values(self.errorModel.attributes)[0];
if (self.collection || self.handler) {
(self.collection || self.handler).trigger(
'pgadmin-session:model:invalid', msg, self, (self.collection || self.handler)
);
} else {
self.trigger('pgadmin-session:invalid', msg, self);
}
}
return res;
}
return res;
},
/*
* We do support modified data only through session by tracking changes.
*
* In normal mode, we will use the toJSON function of Backbone.Model.
* In session mode, we will return all the modified data only. And, the
* objects (collection, and model) will be return as stringified JSON,
* only from the parent object.
*/
toJSON: function(session, method) {
var self = this,
res, isNew = self.isNew();
session = (typeof(session) != 'undefined' && session == true);
if (!session || isNew) {
res = Backbone.Model.prototype.toJSON.call(this, arguments);
} else {
res = {};
res[self.idAttribute || '_id'] = self.get(self.idAttribute || '_id');
res = _.extend(res, self.sessAttrs);
}
/*
* We do have number objects (models, collections), which needs to be
* converted to JSON data manually.
*/
_.each(
self.objects,
function(k) {
var obj = self.get(k);
/*
* For session changes, we only need the modified data to be
* transformed to JSON data.
*/
if (session) {
if (res[k] instanceof Array) {
res[k] = JSON.stringify(res[k]);
} else if ((obj && obj.sessChanged && obj.sessChanged()) || isNew) {
res[k] = obj && obj.toJSON(!isNew);
/*
* We will run JSON.stringify(..) only from the main object,
* not for the JSON object within the objects, that only when
* HTTP method is 'GET'.
*
* We do stringify the object, so that - it will not be
* translated to wierd format using jQuery.
*/
if (obj && method && method == 'GET') {
res[k] = JSON.stringify(res[k]);
}
} else {
delete res[k];
}
} else if (!(res[k] instanceof Array)) {
res[k] = (obj && obj.toJSON());
}
});
if (session) {
_.each(
self.arrays,
function(a) {
/*
* For session changes, we only need the modified data to be
* transformed to JSON data.
*/
if (res[a] && res[a] instanceof Array) {
res[a] = JSON.stringify(res[a]);
}
});
}
return res;
},
startNewSession: function() {
var self = this;
if (self.trackChanges) {
self.trigger('pgadmin-session:stop', self);
self.off('pgadmin-session:model:invalid', self.onChildInvalid);
self.off('pgadmin-session:model:valid', self.onChildValid);
self.off('pgadmin-session:changed', self.onChildChanged);
self.off('pgadmin-session:added', self.onChildChanged);
self.off('pgadmin-session:removed', self.onChildChanged);
}
self.trackChanges = true;
self.sessAttrs = {};
self.origSessAttrs = _.clone(self.attributes);
_.each(self.objects, function(o) {
var obj = self.get(o);
if (_.isUndefined(obj) || _.isNull(obj)) {
return;
}
delete self.origSessAttrs[o];
if (obj && 'startNewSession' in obj && _.isFunction(obj.startNewSession)) {
obj.startNewSession();
}
});
// Let people know, I have started session hanlding
self.trigger('pgadmin-session:start', self);
// Let me listen to the my child invalid/valid messages
self.on('pgadmin-session:model:invalid', self.onChildInvalid);
self.on('pgadmin-session:model:valid', self.onChildValid);
self.on('pgadmin-session:changed', self.onChildChanged);
self.on('pgadmin-session:added', self.onChildChanged);
self.on('pgadmin-session:removed', self.onChildChanged);
},
onChildInvalid: function(msg, obj) {
var self = this;
if (self.trackChanges && obj) {
var objName = obj.attrName;
if (!objName) {
var hasPrimaryKey = obj.primary_key &&
typeof(obj.primary_key) === 'function';
var key = hasPrimaryKey ? obj.primary_key() : obj.cid,
comparator = hasPrimaryKey ?
function(k) {
var o = self.get('k');
if (o && o.primary_key() === key) {
objName = k;
return true;
}
return false;
} :
function(k) {
var o = self.get(k);
if (o.cid === key) {
objName = k;
return true;
}
return false;
};
_.findIndex(self.objects, comparator);
}
if (objName) {
/*
* Update the error message for this object.
*/
self.errorModel.set(objName, msg);
if (self.handler) {
(self.handler).trigger('pgadmin-session:model:invalid', msg, self, self.handler);
} else {
self.trigger('pgadmin-session:invalid', msg, self);
}
}
}
return this;
},
onChildValid: function(obj) {
var self = this;
if (self.trackChanges && obj) {
var objName = obj.attrName;
if (!objName) {
var hasPrimaryKey = (obj.primary_key &&
(typeof(obj.primary_key) === 'function'));
var key = hasPrimaryKey ? obj.primary_key() : obj.cid,
comparator = hasPrimaryKey ?
function(k) {
var o = self.get('k');
if (o && o.primary_key() === key) {
objName = k;
return true;
}
return false;
} :
function(k) {
var o = self.get('k');
if (o && o.cid === key) {
objName = k;
return true;
}
return false;
};
_.findIndex(self.objects, comparator);
}
var msg = null,
validate = function(m, attrs) {
if ('default_validate' in m && typeof(m.default_validate) == 'function') {
msg = m.default_validate();
if (_.isString(msg)) {
return msg;
}
}
if ('validate' in m && typeof(m.validate) == 'function') {
msg = m.validate(attrs);
return msg;
}
return null;
};
if (obj instanceof Backbone.Collection) {
for (var idx in obj.models) {
if (validate(obj.models[idx]))
break;
}
} else if (obj instanceof Backbone.Model) {
validate(obj);
}
if (objName && self.errorModel.has(objName)) {
if (!msg) {
self.errorModel.unset(objName);
} else {
self.errorModel.set(objName, msg);
}
}
/*
* The object is valid, but does this has any effect on parent?
* Let's validate this object itself, before making it clear.
*
* We will trigger validation information.
*/
if (_.size(self.errorModel.attributes) == 0 &&
!validate(self, (objName && [objName]))) {
if (self.handler) {
(self.handler).trigger('pgadmin-session:model:valid', self, self.handler);
} else {
self.trigger(
'pgadmin-session:valid', self.sessChanged(), self
);
}
} else {
msg = msg || _.values(self.errorModel.attributes)[0];
if (self.handler) {
(self.handler).trigger(
'pgadmin-session:model:invalid', msg, self, self.handler
);
} else {
self.trigger('pgadmin-session:invalid', msg, self);
}
}
}
},
onChildChanged: function() {
var self = this;
if (self.trackChanges && self.collection) {
(self.collection).trigger('change', self);
}
},
stopSession: function() {
var self = this;
if (self.trackChanges) {
self.off('pgadmin-session:model:invalid', self.onChildInvalid);
self.off('pgadmin-session:model:valid', self.onChildValid);
self.off('pgadmin-session:changed', self.onChildChanged);
self.off('pgadmin-session:added', self.onChildChanged);
self.off('pgadmin-session:removed', self.onChildChanged);
}
self.trackChanges = false;
self.sessAttrs = {};
self.origSessAttrs = {};
_.each(self.objects, function(o) {
var obj = self.get(o);
if (_.isUndefined(obj) || _.isNull(obj)) {
return;
}
self.origSessAttrs[o] = null;
delete self.origSessAttrs[o];
if (obj && 'stopSession' in obj && _.isFunction(obj.stopSession)) {
obj.stopSession();
}
});
self.trigger('pgadmin-session:stop');
},
default_validate: function() {
var msg, field, value, type;
for (var i = 0, keys = _.keys(this.attributes), l = keys.length; i < l; i++) {
value = this.attributes[keys[i]];
field = this.fieldData[keys[i]];
msg = null;
if (!(_.isUndefined(value) || _.isNull(value) || String(value) === '')) {
if (!field) {
continue;
}
type = field.type || undefined;
if (!type) {
continue;
}
switch (type) {
case 'int':
msg = this.integer_validate(value, field);
break;
case 'numeric':
msg = this.number_validate(value, field);
break;
}
if (msg) {
this.errorModel.set(field.id, msg);
return msg;
} else {
this.errorModel.unset(field.id);
}
} else {
if (field) {
this.errorModel.unset(field.id);
}
}
}
return null;
},
check_min_max: function(value, field) {
var label = field.label,
min_value = field.min,
max_value = field.max;
if (!_.isUndefined(min_value) && value < min_value) {
return pgadminUtils.sprintf(pgAdmin.Browser.messages.MUST_GR_EQ, label, min_value);
} else if (!_.isUndefined(max_value) && value > max_value) {
return pgadminUtils.sprintf(pgAdmin.Browser.messages.MUST_LESS_EQ, label, max_value);
}
return null;
},
number_validate: function(value, field) {
var pattern = new RegExp('^-?[0-9]+(\.?[0-9]*)?$');
if (!pattern.test(value)) {
return pgadminUtils.sprintf(pgAdmin.Browser.messages.MUST_BE_NUM, field.label);
}
return this.check_min_max(value, field);
},
integer_validate: function(value, field) {
var pattern = new RegExp('^-?[0-9]*$');
if (!pattern.test(value)) {
return pgadminUtils.sprintf(pgAdmin.Browser.messages.MUST_BE_INT, field.label);
}
return this.check_min_max(value, field);
},
});
pgBrowser.DataCollection = Backbone.Collection.extend({
// Model collection
initialize: function(attributes, options) {
var self = this;
options = options || {};
/*
* Session changes will be kept in this object.
*/
self.sessAttrs = {
'changed': [],
'added': [],
'deleted': [],
'invalid': [],
};
self.top = options.top || self;
self.attrName = options.attrName;
self.handler = options.handler;
self.trackChanges = false;
/*
* Listen to the model changes for the session changes.
*/
self.on('add', self.onModelAdd);
self.on('remove', self.onModelRemove);
self.on('change', self.onModelChange);
/*
* We need to start the session, if the handler is already in session
* tracking mode.
*/
if (self.handler && self.handler.trackChanges) {
self.startNewSession();
}
return self;
},
startNewSession: function() {
var self = this,
msg;
if (self.trackChanges) {
// We're stopping the existing session.
self.trigger('pgadmin-session:stop', self);
self.off('pgadmin-session:model:invalid', self.onModelInvalid);
self.off('pgadmin-session:model:valid', self.onModelValid);
}
self.trackChanges = true;
self.sessAttrs = {
'changed': [],
'added': [],
'deleted': [],
'invalid': [],
};
_.each(self.models, function(m) {
if ('startNewSession' in m && _.isFunction(m.startNewSession)) {
m.startNewSession();
}
if ('default_validate' in m && typeof(m.default_validate) == 'function') {
msg = m.default_validate();
}
if (_.isString(msg)) {
self.sessAttrs['invalid'][m.cid] = msg;
} else if ('validate' in m && typeof(m.validate) === 'function') {
msg = m.validate();
if (msg) {
self.sessAttrs['invalid'][m.cid] = msg;
}
}
});
// Let people know, I have started session hanlding
self.trigger('pgadmin-session:start', self);
self.on('pgadmin-session:model:invalid', self.onModelInvalid);
self.on('pgadmin-session:model:valid', self.onModelValid);
},
onModelInvalid: function(msg, m) {
var self = this,
invalidModels = self.sessAttrs['invalid'];
if (self.trackChanges) {
// Do not add the existing invalid object
invalidModels[m.cid] = msg;
// Inform the parent that - I am an invalid model.
if (self.handler) {
(self.handler).trigger('pgadmin-session:model:invalid', msg, self, self.handler);
} else {
self.trigger('pgadmin-session:invalid', msg, self);
}
}
return true;
},
onModelValid: function(m) {
var self = this,
invalidModels = self.sessAttrs['invalid'];
if (self.trackChanges) {
// Now check uniqueness of current model with other models.
var isUnique = self.checkDuplicateWithModel(m);
// If unique then find the object the invalid list, if found remove it from the list
// and inform the parent that - I am a valid object now.
if (isUnique && m.cid in invalidModels) {
delete invalidModels[m.cid];
}
if (isUnique) {
this.triggerValidationEvent.apply(this);
}
}
return true;
},
stopSession: function() {
var self = this;
self.trackChanges = false;
self.sessAttrs = {
'changed': [],
'added': [],
'deleted': [],
'invalid': [],
};
_.each(self.models, function(m) {
if ('stopSession' in m && _.isFunction(m.stopSession)) {
m.stopSession();
}
});
},
sessChanged: function() {
return (
this.sessAttrs['changed'].length > 0 ||
this.sessAttrs['added'].length > 0 ||
this.sessAttrs['deleted'].length > 0
);
},
/*
* We do support the changes through session tracking in general.
*
* In normal mode, we will use the general toJSON(..) function of
* Backbone.Colletion.
*
* In session mode, we will return session changes as:
* We will be returning the session changes as:
* {
* 'added': [JSON of each new model],
* 'delete': [JSON of each deleted model],
* 'changed': [JSON of each modified model with session changes]
* }
*/
toJSON: function(session) {
var self = this;
session = (typeof(session) != 'undefined' && session == true);
if (!session) {
return Backbone.Collection.prototype.toJSON.call(self);
} else {
var res = {};
res['added'] = [];
_.each(this.sessAttrs['added'], function(o) {
res['added'].push(o.toJSON());
});
if (res['added'].length == 0) {
delete res['added'];
}
res['changed'] = [];
_.each(self.sessAttrs['changed'], function(o) {
res['changed'].push(o.toJSON(true));
});
if (res['changed'].length == 0) {
delete res['changed'];
}
res['deleted'] = [];
_.each(self.sessAttrs['deleted'], function(o) {
res['deleted'].push(o.toJSON());
});
if (res['deleted'].length == 0) {
delete res['deleted'];
}
return (_.size(res) == 0 ? null : res);
}
},
// Override the reset function, so that - we can reset the model
// properly.
reset: function(_set, opts) {
if (opts && opts.stop)
this.stopSession();
this.each(function(m) {
if (!m)
return;
if (m instanceof pgBrowser.DataModel) {
m.reset(opts);
} else {
m.clear(opts);
}
});
Backbone.Collection.prototype.reset.apply(this, arguments);
},
objFindInSession: function(m, type) {
var hasPrimaryKey = m.primary_key &&
typeof(m.primary_key) == 'function',
key = hasPrimaryKey ? m.primary_key() : m.cid,
comparator = hasPrimaryKey ? function(o) {
return (o.primary_key() === key);
} : function(o) {
return (o.cid === key);
};
return (_.findIndex(this.sessAttrs[type], comparator));
},
onModelAdd: function(obj) {
if (this.trackChanges) {
var self = this,
msg,
idx = self.objFindInSession(obj, 'deleted');
// Hmm.. - it was originally deleted from this collection, we should
// remove it from the 'deleted' list.
if (idx >= 0) {
var origObj = self.sessAttrs['deleted'][idx];
obj.origSessAttrs = _.clone(origObj.origSessAttrs);
obj.attributes = _.extend(obj.attributes, origObj.attributes);
obj.sessAttrs = _.clone(origObj.sessAttrs);
self.sessAttrs['deleted'].splice(idx, 1);
// It has been changed originally!
if ((!('sessChanged' in obj)) || obj.sessChanged()) {
self.sessAttrs['changed'].push(obj);
}
(self.handler || self).trigger('pgadmin-session:added', self, obj);
if ('default_validate' in obj && typeof(obj.default_validate) == 'function') {
msg = obj.default_validate();
}
if (_.isString(msg)) {
(self.sessAttrs['invalid'])[obj.cid] = msg;
} else if ('validate' in obj && typeof(obj.validate) === 'function') {
msg = obj.validate();
if (msg) {
(self.sessAttrs['invalid'])[obj.cid] = msg;
}
}
} else {
if ('default_validate' in obj && typeof(obj.default_validate) == 'function') {
msg = obj.default_validate();
}
if (_.isString(msg)) {
(self.sessAttrs['invalid'])[obj.cid] = msg;
} else if ('validate' in obj && typeof(obj.validate) === 'function') {
msg = obj.validate();
if (msg) {
(self.sessAttrs['invalid'])[obj.cid] = msg;
}
}
self.sessAttrs['added'].push(obj);
/*
* Session has been changed
*/
(self.handler || self).trigger('pgadmin-session:added', self, obj);
}
// Let the parent/listener know about my status (valid/invalid).
this.triggerValidationEvent.apply(this);
}
return true;
},
onModelRemove: function(obj) {
if (this.trackChanges) {
/* Once model is removed from collection clear its errorModel as it's no longer relevant
* for us. Otherwise it creates problem in 'clearInvalidSessionIfModelValid' function.
*/
obj.errorModel.clear();
var self = this,
invalidModels = self.sessAttrs['invalid'],
copy = _.clone(obj),
idx = self.objFindInSession(obj, 'added');
// We need to remove it from the invalid object list first.
if (obj.cid in invalidModels) {
delete invalidModels[obj.cid];
}
// Hmm - it was newly added, we can safely remove it.
if (idx >= 0) {
self.sessAttrs['added'].splice(idx, 1);
(self.handler || self).trigger('pgadmin-session:removed', self, copy);
self.checkDuplicateWithModel(copy);
// Let the parent/listener know about my status (valid/invalid).
this.triggerValidationEvent.apply(this);
} else {
// Hmm - it was changed in this session, we should remove it from the
// changed models.
idx = self.objFindInSession(obj, 'changed');
if (idx >= 0) {
self.sessAttrs['changed'].splice(idx, 1);
(self.handler || self).trigger('pgadmin-session:removed', self, copy);
} else {
(self.handler || self).trigger('pgadmin-session:removed', self, copy);
}
self.sessAttrs['deleted'].push(obj);
self.checkDuplicateWithModel(obj);
// Let the parent/listener know about my status (valid/invalid).
this.triggerValidationEvent.apply(this);
}
/*
* This object has been remove, we need to check (if we still have any
* other invalid message pending).
*/
}
return true;
},
triggerValidationEvent: function() {
var self = this,
msg = null,
invalidModels = self.sessAttrs['invalid'],
validModels = [];
for (var key in invalidModels) {
msg = invalidModels[key];
if (msg) {
break;
} else {
// Hmm..
// How come - you have been assigned in invalid list.
// I will make a list of it, and remove it later.
validModels.push(key);
}
}
// Let's remove the un
for (key in validModels) {
delete invalidModels[validModels[key]];
}
if (!msg) {
if (self.handler) {
self.handler.trigger('pgadmin-session:model:valid', self, self.handler);
} else {
self.trigger('pgadmin-session:valid', self.sessChanged(), self);
}
} else {
if (self.handler) {
self.handler.trigger('pgadmin-session:model:invalid', msg, self, self.handler);
} else {
self.trigger('pgadmin-session:invalid', msg, self);
}
}
},
onModelChange: function(obj) {
var self = this;
if (this.trackChanges && obj instanceof pgBrowser.Node.Model) {
var idx = self.objFindInSession(obj, 'added');
// It was newly added model, we don't need to add into the changed
// list.
if (idx >= 0) {
(self.handler || self).trigger('pgadmin-session:changed', self, obj);
} else {
idx = self.objFindInSession(obj, 'changed');
if (!('sessChanged' in obj)) {
(self.handler || self).trigger('pgadmin-session:changed', self, obj);
if (idx < 0) {
self.sessAttrs['changed'].push(obj);
}
} else {
if (idx >= 0) {
if (!obj.sessChanged()) {
// This object is no more updated, removing it from the changed
// models list.
self.sessAttrs['changed'].splice(idx, 1);
(self.handler || self).trigger('pgadmin-session:changed', self, obj);
} else {
(self.handler || self).trigger('pgadmin-session:changed', self, obj);
}
} else if (obj.sessChanged()) {
self.sessAttrs['changed'].push(obj);
(self.handler || self).trigger('pgadmin-session:changed', self, obj);
}
}
}
}
return true;
},
/*
* This function will check if given model is unique or duplicate in
* collection and set/clear duplicate errors on models.
*/
checkDuplicateWithModel: function(model) {
if (!('keys' in model) || _.isEmpty(model.keys)) {
return true;
}
var self = this,
condition = {},
previous_condition = {};
_.each(model.keys, function(key) {
condition[key] = model.get(key);
if (key in model._previous_key_values) {
previous_condition[key] = model._previous_key_values[key];
} else {
previous_condition[key] = model.previous(key);
}
});
// Reset previously changed values.
model._previous_key_values = {};
var old_conflicting_models = self.where(previous_condition);
if (old_conflicting_models.length == 1) {
var m = old_conflicting_models[0];
self.clearInvalidSessionIfModelValid(m);
}
var new_conflicting_models = self.where(condition);
if (new_conflicting_models.length == 0) {
self.clearInvalidSessionIfModelValid(model);
} else if (new_conflicting_models.length == 1) {
self.clearInvalidSessionIfModelValid(model);
self.clearInvalidSessionIfModelValid(new_conflicting_models[0]);
} else {
var msg = 'Duplicate rows.';
setTimeout(function() {
_.each(new_conflicting_models, function(local_model) {
self.trigger(
'pgadmin-session:model:invalid', msg, local_model, self.handler
);
local_model.trigger(
'pgadmin-session:model:duplicate', local_model, msg
);
});
}, 10);
return false;
}
return true;
},
clearInvalidSessionIfModelValid: function(m) {
var errors = m.errorModel.attributes,
invalidModels = this.sessAttrs['invalid'];
m.trigger('pgadmin-session:model:unique', m);
if (_.size(errors) == 0) {
delete invalidModels[m.cid];
} else {
invalidModels[m.cid] = errors[Object.keys(errors)[0]];
}
},
});
Introduced a event manager for the browser, which will generate certain known events, when any activity happens on the browser layout. The following types of events will be triggered through the browser event manager. - pgadmin-browser:frame:* [1] - pgadmin-browser:frame-<name>:* [1] - pgadmin-browser:panel:* [1] - pgadmin-browser:panel-<name>:* [1] - pgadmin-browser:panel - pgadmin-browser:frame - pgadmin-browser:tree - pgadmin-browser:tree:* [2] [1] The '*' denotes some of the events generated by the wcDocker, which can be useful to do some operations. These events are: + wcDocker.EVENT.UPDATED + wcDocker.EVENT.VISIBILITY_CHANGED + wcDocker.EVENT.BEGIN_DOCK + wcDocker.EVENT.END_DOCK + wcDocker.EVENT.GAIN_FOCUS, + wcDocker.EVENT.LOST_FOCUS + wcDocker.EVENT.CLOSED + wcDocker.EVENT.BUTTON + wcDocker.EVENT.ATTACHED + wcDocker.EVENT.DETACHED + wcDocker.EVENT.MOVE_STARTED + wcDocker.EVENT.MOVE_ENDED + wcDocker.EVENT.MOVED + wcDocker.EVENT.RESIZE_STARTED + wcDocker.EVENT.RESIZE_ENDED + wcDocker.EVENT.RESIZED + wcDocker.EVENT.SCROLLED [2] The '*' denotes all the events generated by the Browser Tree (aciTree). The extension modules can utilize these events to do some operations on nodes, and panels. This patch includes showing 'Reversed Engineered Query' for the selected node (if allowed) using the above approch. The ShowNodeSQL module looks for two events. 1. SQL Panel Visibility change. - Based on the visibility of that panel, we start/stop listening the node selection event. 2. Node Selection in Browser tree - Based on the selected node type, it will look for 'hasSQL' parameter of the node, and fetch the Query from the server, and show it in the SQL editor.
2016-01-19 06:31:14 -06:00
pgBrowser.Events = _.extend({}, Backbone.Events);
return pgBrowser;
});