Re: [PATCH] Persist opened nodes in tree

From: Dave Page <dpage(at)pgadmin(dot)org>
To: Versus Void <chaoskeeper(at)mail(dot)ru>
Cc: pgadmin-hackers(at)lists(dot)postgresql(dot)org
Subject: Re: [PATCH] Persist opened nodes in tree
Date: 2017-07-18 09:30:05
Message-ID: CA+OCxoxP6snXr_zHkZe-QC_qu9rTXEcqNdSO=iJ-AdM+AuGq2w@mail.gmail.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgadmin-hackers

Hi

This would be a welcome feature addition, but why not store the treeview
state in the configuration database? I'd prefer not to add another storage
mechanism without a very compelling reason.

If you need info on how it works, please feel free to ask questions.

Thanks.

On Tue, Jul 18, 2017 at 9:42 AM, Versus Void <chaoskeeper(at)mail(dot)ru> wrote:

> ---
> web/pgadmin/browser/__init__.py | 13 +-
> .../browser/static/vendor/jStorage/jstorage.js | 996
> +++++++++++++++++++++
> .../browser/static/vendor/jStorage/jstorage.min.js | 16 +
> .../browser/templates/browser/js/browser.js | 6 +-
> 4 files changed, 1029 insertions(+), 2 deletions(-)
> create mode 100644 web/pgadmin/browser/static/vendor/jStorage/jstorage.js
> create mode 100644 web/pgadmin/browser/static/
> vendor/jStorage/jstorage.min.js
>
> diff --git a/web/pgadmin/browser/__init__.py
> b/web/pgadmin/browser/__init__.py
> index 77e052f0..ecf3afba 100644
> --- a/web/pgadmin/browser/__init__.py
> +++ b/web/pgadmin/browser/__init__.py
> @@ -100,13 +100,24 @@ class BrowserModule(PgAdminModule):
> 'preloaded': True
> })
> scripts.append({
> + 'name': 'jStorage',
> + 'path': url_for(
> + 'browser.static',
> + filename='vendor/jStorage/jstorage' if
> + current_app.debug else 'vendor/jStorage/jstorage.min'
> + ),
> + 'deps': ['jquery'],
> + 'exports': 'jStorage',
> + 'preloaded': True
> + })
> + scripts.append({
> 'name': 'jquery.acitree',
> 'path': url_for(
> 'browser.static',
> filename='vendor/aciTree/jquery.aciTree' if
> current_app.debug else 'vendor/aciTree/jquery.
> aciTree.min'
> ),
> - 'deps': ['jquery', 'jquery.aciplugin'],
> + 'deps': ['jquery', 'jquery.aciplugin', 'jStorage'],
> 'exports': 'aciPluginClass.plugins.aciTree',
> 'preloaded': True
> })
> diff --git a/web/pgadmin/browser/static/vendor/jStorage/jstorage.js
> b/web/pgadmin/browser/static/vendor/jStorage/jstorage.js
> new file mode 100644
> index 00000000..1ac8fccc
> --- /dev/null
> +++ b/web/pgadmin/browser/static/vendor/jStorage/jstorage.js
> @@ -0,0 +1,996 @@
> +/*
> + * ----------------------------- JSTORAGE ------------------------------
> -------
> + * Simple local storage wrapper to save data on the browser side,
> supporting
> + * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera
> 10.5+
> + *
> + * Author: Andris Reinman, andris(dot)reinman(at)gmail(dot)com
> + * Project homepage: www.jstorage.info
> + *
> + * Licensed under Unlicense:
> + *
> + * This is free and unencumbered software released into the public domain.
> + *
> + * Anyone is free to copy, modify, publish, use, compile, sell, or
> + * distribute this software, either in source code form or as a compiled
> + * binary, for any purpose, commercial or non-commercial, and by any
> + * means.
> + *
> + * In jurisdictions that recognize copyright laws, the author or authors
> + * of this software dedicate any and all copyright interest in the
> + * software to the public domain. We make this dedication for the benefit
> + * of the public at large and to the detriment of our heirs and
> + * successors. We intend this dedication to be an overt act of
> + * relinquishment in perpetuity of all present and future rights to this
> + * software under copyright law.
> + *
> + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
> + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
> + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
> + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
> + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
> + * OTHER DEALINGS IN THE SOFTWARE.
> + *
> + * For more information, please refer to <http://unlicense.org/>
> + */
> +
> +/* global ActiveXObject: false */
> +/* jshint browser: true */
> +
> +(function() {
> + 'use strict';
> +
> + var
> + /* jStorage version */
> + JSTORAGE_VERSION = '0.4.12',
> +
> + /* detect a dollar object or create one if not found */
> + $ = window.jQuery || window.$ || (window.$ = {}),
> +
> + /* check for a JSON handling support */
> + JSON = {
> + parse: window.JSON && (window.JSON.parse ||
> window.JSON.decode) ||
> + String.prototype.evalJSON && function(str) {
> + return String(str).evalJSON();
> + } ||
> + $.parseJSON ||
> + $.evalJSON,
> + stringify: Object.toJSON ||
> + window.JSON && (window.JSON.stringify ||
> window.JSON.encode) ||
> + $.toJSON
> + };
> +
> + // Break if no JSON support was found
> + if (typeof JSON.parse !== 'function' || typeof JSON.stringify !==
> 'function') {
> + throw new Error('No JSON support found, include //
> cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page');
> + }
> +
> + var
> + /* This is the object, that holds the cached values */
> + _storage = {
> + __jstorage_meta: {
> + CRC32: {}
> + }
> + },
> +
> + /* Actual browser storage (localStorage or
> globalStorage['domain']) */
> + _storage_service = {
> + jStorage: '{}'
> + },
> +
> + /* DOM element for older IE versions, holds userData behavior */
> + _storage_elm = null,
> +
> + /* How much space does the storage take */
> + _storage_size = 0,
> +
> + /* which backend is currently used */
> + _backend = false,
> +
> + /* onchange observers */
> + _observers = {},
> +
> + /* timeout to wait after onchange event */
> + _observer_timeout = false,
> +
> + /* last update time */
> + _observer_update = 0,
> +
> + /* pubsub observers */
> + _pubsub_observers = {},
> +
> + /* skip published items older than current timestamp */
> + _pubsub_last = +new Date(),
> +
> + /* Next check for TTL */
> + _ttl_timeout,
> +
> + /**
> + * XML encoding and decoding as XML nodes can't be JSON'ized
> + * XML nodes are encoded and decoded if the node is the value to
> be saved
> + * but not if it's as a property of another object
> + * Eg. -
> + * $.jStorage.set('key', xmlNode); // IS OK
> + * $.jStorage.set('key', {xml: xmlNode}); // NOT OK
> + */
> + _XMLService = {
> +
> + /**
> + * Validates a XML node to be XML
> + * based on jQuery.isXML function
> + */
> + isXML: function(elm) {
> + var documentElement = (elm ? elm.ownerDocument || elm :
> 0).documentElement;
> + return documentElement ? documentElement.nodeName !==
> 'HTML' : false;
> + },
> +
> + /**
> + * Encodes a XML node to string
> + * based on http://www.mercurytide.co.uk/
> news/article/issues-when-working-ajax/
> + */
> + encode: function(xmlNode) {
> + if (!this.isXML(xmlNode)) {
> + return false;
> + }
> + try { // Mozilla, Webkit, Opera
> + return new XMLSerializer().
> serializeToString(xmlNode);
> + } catch (E1) {
> + try { // IE
> + return xmlNode.xml;
> + } catch (E2) {}
> + }
> + return false;
> + },
> +
> + /**
> + * Decodes a XML node from string
> + * loosely based on http://outwestmedia.com/
> jquery-plugins/xmldom/
> + */
> + decode: function(xmlString) {
> + var dom_parser = ('DOMParser' in window && (new
> DOMParser()).parseFromString) ||
> + (window.ActiveXObject && function(_xmlString) {
> + var xml_doc = new ActiveXObject('Microsoft.
> XMLDOM');
> + xml_doc.async = 'false';
> + xml_doc.loadXML(_xmlString);
> + return xml_doc;
> + }),
> + resultXML;
> + if (!dom_parser) {
> + return false;
> + }
> + resultXML = dom_parser.call('DOMParser' in window && (new
> DOMParser()) || window, xmlString, 'text/xml');
> + return this.isXML(resultXML) ? resultXML : false;
> + }
> + };
> +
> +
> + ////////////////////////// PRIVATE METHODS ////////////////////////
> +
> + /**
> + * Initialization function. Detects if the browser supports DOM
> Storage
> + * or userData behavior and behaves accordingly.
> + */
> + function _init() {
> + /* Check if browser supports localStorage */
> + var localStorageReallyWorks = false;
> + if ('localStorage' in window) {
> + try {
> + window.localStorage.setItem('_tmptest', 'tmpval');
> + localStorageReallyWorks = true;
> + window.localStorage.removeItem('_tmptest');
> + } catch (BogusQuotaExceededErrorOnIos5) {
> + // Thanks be to iOS5 Private Browsing mode which throws
> + // QUOTA_EXCEEDED_ERRROR DOM Exception 22.
> + }
> + }
> +
> + if (localStorageReallyWorks) {
> + try {
> + if (window.localStorage) {
> + _storage_service = window.localStorage;
> + _backend = 'localStorage';
> + _observer_update = _storage_service.jStorage_update;
> + }
> + } catch (E3) { /* Firefox fails when touching localStorage
> and cookies are disabled */ }
> + }
> + /* Check if browser supports globalStorage */
> + else if ('globalStorage' in window) {
> + try {
> + if (window.globalStorage) {
> + if (window.location.hostname == 'localhost') {
> + _storage_service = window.globalStorage['
> localhost.localdomain'];
> + } else {
> + _storage_service = window.globalStorage[window.
> location.hostname];
> + }
> + _backend = 'globalStorage';
> + _observer_update = _storage_service.jStorage_update;
> + }
> + } catch (E4) { /* Firefox fails when touching localStorage
> and cookies are disabled */ }
> + }
> + /* Check if browser supports userData behavior */
> + else {
> + _storage_elm = document.createElement('link');
> + if (_storage_elm.addBehavior) {
> +
> + /* Use a DOM element to act as userData storage */
> + _storage_elm.style.behavior = 'url(#default#userData)';
> +
> + /* userData element needs to be inserted into the DOM! */
> + document.getElementsByTagName('head')[0].appendChild(_
> storage_elm);
> +
> + try {
> + _storage_elm.load('jStorage');
> + } catch (E) {
> + // try to reset cache
> + _storage_elm.setAttribute('jStorage', '{}');
> + _storage_elm.save('jStorage');
> + _storage_elm.load('jStorage');
> + }
> +
> + var data = '{}';
> + try {
> + data = _storage_elm.getAttribute('jStorage');
> + } catch (E5) {}
> +
> + try {
> + _observer_update = _storage_elm.getAttribute('
> jStorage_update');
> + } catch (E6) {}
> +
> + _storage_service.jStorage = data;
> + _backend = 'userDataBehavior';
> + } else {
> + _storage_elm = null;
> + return;
> + }
> + }
> +
> + // Load data from storage
> + _load_storage();
> +
> + // remove dead keys
> + _handleTTL();
> +
> + // start listening for changes
> + _setupObserver();
> +
> + // initialize publish-subscribe service
> + _handlePubSub();
> +
> + // handle cached navigation
> + if ('addEventListener' in window) {
> + window.addEventListener('pageshow', function(event) {
> + if (event.persisted) {
> + _storageObserver();
> + }
> + }, false);
> + }
> + }
> +
> + /**
> + * Reload data from storage when needed
> + */
> + function _reloadData() {
> + var data = '{}';
> +
> + if (_backend == 'userDataBehavior') {
> + _storage_elm.load('jStorage');
> +
> + try {
> + data = _storage_elm.getAttribute('jStorage');
> + } catch (E5) {}
> +
> + try {
> + _observer_update = _storage_elm.getAttribute('
> jStorage_update');
> + } catch (E6) {}
> +
> + _storage_service.jStorage = data;
> + }
> +
> + _load_storage();
> +
> + // remove dead keys
> + _handleTTL();
> +
> + _handlePubSub();
> + }
> +
> + /**
> + * Sets up a storage change observer
> + */
> + function _setupObserver() {
> + if (_backend == 'localStorage' || _backend == 'globalStorage') {
> + if ('addEventListener' in window) {
> + window.addEventListener('storage', _storageObserver,
> false);
> + } else {
> + document.attachEvent('onstorage', _storageObserver);
> + }
> + } else if (_backend == 'userDataBehavior') {
> + setInterval(_storageObserver, 1000);
> + }
> + }
> +
> + /**
> + * Fired on any kind of data change, needs to check if anything has
> + * really been changed
> + */
> + function _storageObserver() {
> + var updateTime;
> + // cumulate change notifications with timeout
> + clearTimeout(_observer_timeout);
> + _observer_timeout = setTimeout(function() {
> +
> + if (_backend == 'localStorage' || _backend ==
> 'globalStorage') {
> + updateTime = _storage_service.jStorage_update;
> + } else if (_backend == 'userDataBehavior') {
> + _storage_elm.load('jStorage');
> + try {
> + updateTime = _storage_elm.getAttribute('
> jStorage_update');
> + } catch (E5) {}
> + }
> +
> + if (updateTime && updateTime != _observer_update) {
> + _observer_update = updateTime;
> + _checkUpdatedKeys();
> + }
> +
> + }, 25);
> + }
> +
> + /**
> + * Reloads the data and checks if any keys are changed
> + */
> + function _checkUpdatedKeys() {
> + var oldCrc32List = JSON.parse(JSON.stringify(_
> storage.__jstorage_meta.CRC32)),
> + newCrc32List;
> +
> + _reloadData();
> + newCrc32List = JSON.parse(JSON.stringify(_
> storage.__jstorage_meta.CRC32));
> +
> + var key,
> + updated = [],
> + removed = [];
> +
> + for (key in oldCrc32List) {
> + if (oldCrc32List.hasOwnProperty(key)) {
> + if (!newCrc32List[key]) {
> + removed.push(key);
> + continue;
> + }
> + if (oldCrc32List[key] != newCrc32List[key] &&
> String(oldCrc32List[key]).substr(0, 2) == '2.') {
> + updated.push(key);
> + }
> + }
> + }
> +
> + for (key in newCrc32List) {
> + if (newCrc32List.hasOwnProperty(key)) {
> + if (!oldCrc32List[key]) {
> + updated.push(key);
> + }
> + }
> + }
> +
> + _fireObservers(updated, 'updated');
> + _fireObservers(removed, 'deleted');
> + }
> +
> + /**
> + * Fires observers for updated keys
> + *
> + * @param {Array|String} keys Array of key names or a key
> + * @param {String} action What happened with the value (updated,
> deleted, flushed)
> + */
> + function _fireObservers(keys, action) {
> + keys = [].concat(keys || []);
> +
> + var i, j, len, jlen;
> +
> + if (action == 'flushed') {
> + keys = [];
> + for (var key in _observers) {
> + if (_observers.hasOwnProperty(key)) {
> + keys.push(key);
> + }
> + }
> + action = 'deleted';
> + }
> + for (i = 0, len = keys.length; i < len; i++) {
> + if (_observers[keys[i]]) {
> + for (j = 0, jlen = _observers[keys[i]].length; j < jlen;
> j++) {
> + _observers[keys[i]][j](keys[i], action);
> + }
> + }
> + if (_observers['*']) {
> + for (j = 0, jlen = _observers['*'].length; j < jlen; j++)
> {
> + _observers['*'][j](keys[i], action);
> + }
> + }
> + }
> + }
> +
> + /**
> + * Publishes key change to listeners
> + */
> + function _publishChange() {
> + var updateTime = (+new Date()).toString();
> +
> + if (_backend == 'localStorage' || _backend == 'globalStorage') {
> + try {
> + _storage_service.jStorage_update = updateTime;
> + } catch (E8) {
> + // safari private mode has been enabled after the
> jStorage initialization
> + _backend = false;
> + }
> + } else if (_backend == 'userDataBehavior') {
> + _storage_elm.setAttribute('jStorage_update', updateTime);
> + _storage_elm.save('jStorage');
> + }
> +
> + _storageObserver();
> + }
> +
> + /**
> + * Loads the data from the storage based on the supported mechanism
> + */
> + function _load_storage() {
> + /* if jStorage string is retrieved, then decode it */
> + if (_storage_service.jStorage) {
> + try {
> + _storage = JSON.parse(String(_storage_service.jStorage));
> + } catch (E6) {
> + _storage_service.jStorage = '{}';
> + }
> + } else {
> + _storage_service.jStorage = '{}';
> + }
> + _storage_size = _storage_service.jStorage ?
> String(_storage_service.jStorage).length : 0;
> +
> + if (!_storage.__jstorage_meta) {
> + _storage.__jstorage_meta = {};
> + }
> + if (!_storage.__jstorage_meta.CRC32) {
> + _storage.__jstorage_meta.CRC32 = {};
> + }
> + }
> +
> + /**
> + * This functions provides the 'save' mechanism to store the jStorage
> object
> + */
> + function _save() {
> + _dropOldEvents(); // remove expired events
> + try {
> + _storage_service.jStorage = JSON.stringify(_storage);
> + // If userData is used as the storage engine, additional
> + if (_storage_elm) {
> + _storage_elm.setAttribute('jStorage',
> _storage_service.jStorage);
> + _storage_elm.save('jStorage');
> + }
> + _storage_size = _storage_service.jStorage ?
> String(_storage_service.jStorage).length : 0;
> + } catch (E7) { /* probably cache is full, nothing is saved this
> way*/ }
> + }
> +
> + /**
> + * Function checks if a key is set and is string or numberic
> + *
> + * @param {String} key Key name
> + */
> + function _checkKey(key) {
> + if (typeof key != 'string' && typeof key != 'number') {
> + throw new TypeError('Key name must be string or numeric');
> + }
> + if (key == '__jstorage_meta') {
> + throw new TypeError('Reserved key name');
> + }
> + return true;
> + }
> +
> + /**
> + * Removes expired keys
> + */
> + function _handleTTL() {
> + var curtime, i, TTL, CRC32, nextExpire = Infinity,
> + changed = false,
> + deleted = [];
> +
> + clearTimeout(_ttl_timeout);
> +
> + if (!_storage.__jstorage_meta || typeof
> _storage.__jstorage_meta.TTL != 'object') {
> + // nothing to do here
> + return;
> + }
> +
> + curtime = +new Date();
> + TTL = _storage.__jstorage_meta.TTL;
> +
> + CRC32 = _storage.__jstorage_meta.CRC32;
> + for (i in TTL) {
> + if (TTL.hasOwnProperty(i)) {
> + if (TTL[i] <= curtime) {
> + delete TTL[i];
> + delete CRC32[i];
> + delete _storage[i];
> + changed = true;
> + deleted.push(i);
> + } else if (TTL[i] < nextExpire) {
> + nextExpire = TTL[i];
> + }
> + }
> + }
> +
> + // set next check
> + if (nextExpire != Infinity) {
> + _ttl_timeout = setTimeout(_handleTTL, Math.min(nextExpire -
> curtime, 0x7FFFFFFF));
> + }
> +
> + // save changes
> + if (changed) {
> + _save();
> + _publishChange();
> + _fireObservers(deleted, 'deleted');
> + }
> + }
> +
> + /**
> + * Checks if there's any events on hold to be fired to listeners
> + */
> + function _handlePubSub() {
> + var i, len;
> + if (!_storage.__jstorage_meta.PubSub) {
> + return;
> + }
> + var pubelm,
> + _pubsubCurrent = _pubsub_last,
> + needFired = [];
> +
> + for (i = len = _storage.__jstorage_meta.PubSub.length - 1; i >=
> 0; i--) {
> + pubelm = _storage.__jstorage_meta.PubSub[i];
> + if (pubelm[0] > _pubsub_last) {
> + _pubsubCurrent = pubelm[0];
> + needFired.unshift(pubelm);
> + }
> + }
> +
> + for (i = needFired.length - 1; i >= 0; i--) {
> + _fireSubscribers(needFired[i][1], needFired[i][2]);
> + }
> +
> + _pubsub_last = _pubsubCurrent;
> + }
> +
> + /**
> + * Fires all subscriber listeners for a pubsub channel
> + *
> + * @param {String} channel Channel name
> + * @param {Mixed} payload Payload data to deliver
> + */
> + function _fireSubscribers(channel, payload) {
> + if (_pubsub_observers[channel]) {
> + for (var i = 0, len = _pubsub_observers[channel].length; i <
> len; i++) {
> + // send immutable data that can't be modified by listeners
> + try {
> + _pubsub_observers[channel][i](channel,
> JSON.parse(JSON.stringify(payload)));
> + } catch (E) {}
> + }
> + }
> + }
> +
> + /**
> + * Remove old events from the publish stream (at least 2sec old)
> + */
> + function _dropOldEvents() {
> + if (!_storage.__jstorage_meta.PubSub) {
> + return;
> + }
> +
> + var retire = +new Date() - 2000;
> +
> + for (var i = 0, len = _storage.__jstorage_meta.PubSub.length; i
> < len; i++) {
> + if (_storage.__jstorage_meta.PubSub[i][0] <= retire) {
> + // deleteCount is needed for IE6
> + _storage.__jstorage_meta.PubSub.splice(i,
> _storage.__jstorage_meta.PubSub.length - i);
> + break;
> + }
> + }
> +
> + if (!_storage.__jstorage_meta.PubSub.length) {
> + delete _storage.__jstorage_meta.PubSub;
> + }
> +
> + }
> +
> + /**
> + * Publish payload to a channel
> + *
> + * @param {String} channel Channel name
> + * @param {Mixed} payload Payload to send to the subscribers
> + */
> + function _publish(channel, payload) {
> + if (!_storage.__jstorage_meta) {
> + _storage.__jstorage_meta = {};
> + }
> + if (!_storage.__jstorage_meta.PubSub) {
> + _storage.__jstorage_meta.PubSub = [];
> + }
> +
> + _storage.__jstorage_meta.PubSub.unshift([+new Date(), channel,
> payload]);
> +
> + _save();
> + _publishChange();
> + }
> +
> +
> + /**
> + * JS Implementation of MurmurHash2
> + *
> + * SOURCE: https://github.com/garycourt/murmurhash-js (MIT licensed)
> + *
> + * @author <a href='mailto:gary(dot)court(at)gmail(dot)com'>Gary Court</a>
> + * @see http://github.com/garycourt/murmurhash-js
> + * @author <a href='mailto:aappleby(at)gmail(dot)com'>Austin Appleby</a>
> + * @see http://sites.google.com/site/murmurhash/
> + *
> + * @param {string} str ASCII only
> + * @param {number} seed Positive integer only
> + * @return {number} 32-bit positive integer hash
> + */
> +
> + function murmurhash2_32_gc(str, seed) {
> + var
> + l = str.length,
> + h = seed ^ l,
> + i = 0,
> + k;
> +
> + while (l >= 4) {
> + k =
> + ((str.charCodeAt(i) & 0xff)) |
> + ((str.charCodeAt(++i) & 0xff) << 8) |
> + ((str.charCodeAt(++i) & 0xff) << 16) |
> + ((str.charCodeAt(++i) & 0xff) << 24);
> +
> + k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) *
> 0x5bd1e995) & 0xffff) << 16));
> + k ^= k >>> 24;
> + k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) *
> 0x5bd1e995) & 0xffff) << 16));
> +
> + h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) *
> 0x5bd1e995) & 0xffff) << 16)) ^ k;
> +
> + l -= 4;
> + ++i;
> + }
> +
> + switch (l) {
> + case 3:
> + h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
> + /* falls through */
> + case 2:
> + h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
> + /* falls through */
> + case 1:
> + h ^= (str.charCodeAt(i) & 0xff);
> + h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) *
> 0x5bd1e995) & 0xffff) << 16));
> + }
> +
> + h ^= h >>> 13;
> + h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) &
> 0xffff) << 16));
> + h ^= h >>> 15;
> +
> + return h >>> 0;
> + }
> +
> + ////////////////////////// PUBLIC INTERFACE /////////////////////////
> +
> + $.jStorage = {
> + /* Version number */
> + version: JSTORAGE_VERSION,
> +
> + /**
> + * Sets a key's value.
> + *
> + * @param {String} key Key to set. If this value is not set or not
> + * a string an exception is raised.
> + * @param {Mixed} value Value to set. This can be any value that
> is JSON
> + * compatible (Numbers, Strings, Objects etc.).
> + * @param {Object} [options] - possible options to use
> + * @param {Number} [options.TTL] - optional TTL value, in
> milliseconds
> + * @return {Mixed} the used value
> + */
> + set: function(key, value, options) {
> + _checkKey(key);
> +
> + options = options || {};
> +
> + // undefined values are deleted automatically
> + if (typeof value == 'undefined') {
> + this.deleteKey(key);
> + return value;
> + }
> +
> + if (_XMLService.isXML(value)) {
> + value = {
> + _is_xml: true,
> + xml: _XMLService.encode(value)
> + };
> + } else if (typeof value == 'function') {
> + return undefined; // functions can't be saved!
> + } else if (value && typeof value == 'object') {
> + // clone the object before saving to _storage tree
> + value = JSON.parse(JSON.stringify(value));
> + }
> +
> + _storage[key] = value;
> +
> + _storage.__jstorage_meta.CRC32[key] = '2.' +
> murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c);
> +
> + this.setTTL(key, options.TTL || 0); // also handles saving
> and _publishChange
> +
> + _fireObservers(key, 'updated');
> + return value;
> + },
> +
> + /**
> + * Looks up a key in cache
> + *
> + * @param {String} key - Key to look up.
> + * @param {mixed} def - Default value to return, if key didn't
> exist.
> + * @return {Mixed} the key value, default value or null
> + */
> + get: function(key, def) {
> + _checkKey(key);
> + if (key in _storage) {
> + if (_storage[key] && typeof _storage[key] == 'object' &&
> _storage[key]._is_xml) {
> + return _XMLService.decode(_storage[key].xml);
> + } else {
> + return _storage[key];
> + }
> + }
> + return typeof(def) == 'undefined' ? null : def;
> + },
> +
> + /**
> + * Deletes a key from cache.
> + *
> + * @param {String} key - Key to delete.
> + * @return {Boolean} true if key existed or false if it didn't
> + */
> + deleteKey: function(key) {
> + _checkKey(key);
> + if (key in _storage) {
> + delete _storage[key];
> + // remove from TTL list
> + if (typeof _storage.__jstorage_meta.TTL == 'object' &&
> + key in _storage.__jstorage_meta.TTL) {
> + delete _storage.__jstorage_meta.TTL[key];
> + }
> +
> + delete _storage.__jstorage_meta.CRC32[key];
> +
> + _save();
> + _publishChange();
> + _fireObservers(key, 'deleted');
> + return true;
> + }
> + return false;
> + },
> +
> + /**
> + * Sets a TTL for a key, or remove it if ttl value is 0 or below
> + *
> + * @param {String} key - key to set the TTL for
> + * @param {Number} ttl - TTL timeout in milliseconds
> + * @return {Boolean} true if key existed or false if it didn't
> + */
> + setTTL: function(key, ttl) {
> + var curtime = +new Date();
> + _checkKey(key);
> + ttl = Number(ttl) || 0;
> + if (key in _storage) {
> +
> + if (!_storage.__jstorage_meta.TTL) {
> + _storage.__jstorage_meta.TTL = {};
> + }
> +
> + // Set TTL value for the key
> + if (ttl > 0) {
> + _storage.__jstorage_meta.TTL[key] = curtime + ttl;
> + } else {
> + delete _storage.__jstorage_meta.TTL[key];
> + }
> +
> + _save();
> +
> + _handleTTL();
> +
> + _publishChange();
> + return true;
> + }
> + return false;
> + },
> +
> + /**
> + * Gets remaining TTL (in milliseconds) for a key or 0 when no
> TTL has been set
> + *
> + * @param {String} key Key to check
> + * @return {Number} Remaining TTL in milliseconds
> + */
> + getTTL: function(key) {
> + var curtime = +new Date(),
> + ttl;
> + _checkKey(key);
> + if (key in _storage && _storage.__jstorage_meta.TTL &&
> _storage.__jstorage_meta.TTL[key]) {
> + ttl = _storage.__jstorage_meta.TTL[key] - curtime;
> + return ttl || 0;
> + }
> + return 0;
> + },
> +
> + /**
> + * Deletes everything in cache.
> + *
> + * @return {Boolean} Always true
> + */
> + flush: function() {
> + _storage = {
> + __jstorage_meta: {
> + CRC32: {}
> + }
> + };
> + _save();
> + _publishChange();
> + _fireObservers(null, 'flushed');
> + return true;
> + },
> +
> + /**
> + * Returns a read-only copy of _storage
> + *
> + * @return {Object} Read-only copy of _storage
> + */
> + storageObj: function() {
> + function F() {}
> + F.prototype = _storage;
> + return new F();
> + },
> +
> + /**
> + * Returns an index of all used keys as an array
> + * ['key1', 'key2',..'keyN']
> + *
> + * @return {Array} Used keys
> + */
> + index: function() {
> + var index = [],
> + i;
> + for (i in _storage) {
> + if (_storage.hasOwnProperty(i) && i != '__jstorage_meta')
> {
> + index.push(i);
> + }
> + }
> + return index;
> + },
> +
> + /**
> + * How much space in bytes does the storage take?
> + *
> + * @return {Number} Storage size in chars (not the same as in
> bytes,
> + * since some chars may take several bytes)
> + */
> + storageSize: function() {
> + return _storage_size;
> + },
> +
> + /**
> + * Which backend is currently in use?
> + *
> + * @return {String} Backend name
> + */
> + currentBackend: function() {
> + return _backend;
> + },
> +
> + /**
> + * Test if storage is available
> + *
> + * @return {Boolean} True if storage can be used
> + */
> + storageAvailable: function() {
> + return !!_backend;
> + },
> +
> + /**
> + * Register change listeners
> + *
> + * @param {String} key Key name
> + * @param {Function} callback Function to run when the key changes
> + */
> + listenKeyChange: function(key, callback) {
> + _checkKey(key);
> + if (!_observers[key]) {
> + _observers[key] = [];
> + }
> + _observers[key].push(callback);
> + },
> +
> + /**
> + * Remove change listeners
> + *
> + * @param {String} key Key name to unregister listeners against
> + * @param {Function} [callback] If set, unregister the callback,
> if not - unregister all
> + */
> + stopListening: function(key, callback) {
> + _checkKey(key);
> +
> + if (!_observers[key]) {
> + return;
> + }
> +
> + if (!callback) {
> + delete _observers[key];
> + return;
> + }
> +
> + for (var i = _observers[key].length - 1; i >= 0; i--) {
> + if (_observers[key][i] == callback) {
> + _observers[key].splice(i, 1);
> + }
> + }
> + },
> +
> + /**
> + * Subscribe to a Publish/Subscribe event stream
> + *
> + * @param {String} channel Channel name
> + * @param {Function} callback Function to run when the something
> is published to the channel
> + */
> + subscribe: function(channel, callback) {
> + channel = (channel || '').toString();
> + if (!channel) {
> + throw new TypeError('Channel not defined');
> + }
> + if (!_pubsub_observers[channel]) {
> + _pubsub_observers[channel] = [];
> + }
> + _pubsub_observers[channel].push(callback);
> + },
> +
> + /**
> + * Publish data to an event stream
> + *
> + * @param {String} channel Channel name
> + * @param {Mixed} payload Payload to deliver
> + */
> + publish: function(channel, payload) {
> + channel = (channel || '').toString();
> + if (!channel) {
> + throw new TypeError('Channel not defined');
> + }
> +
> + _publish(channel, payload);
> + },
> +
> + /**
> + * Reloads the data from browser storage
> + */
> + reInit: function() {
> + _reloadData();
> + },
> +
> + /**
> + * Removes reference from global objects and saves it as jStorage
> + *
> + * @param {Boolean} option if needed to save object as simple
> 'jStorage' in windows context
> + */
> + noConflict: function(saveInGlobal) {
> + delete window.$.jStorage;
> +
> + if (saveInGlobal) {
> + window.jStorage = this;
> + }
> +
> + return this;
> + }
> + };
> +
> + // Initialize jStorage
> + _init();
> +
> +})();
> \ No newline at end of file
> diff --git a/web/pgadmin/browser/static/vendor/jStorage/jstorage.min.js
> b/web/pgadmin/browser/static/vendor/jStorage/jstorage.min.js
> new file mode 100644
> index 00000000..ecde658e
> --- /dev/null
> +++ b/web/pgadmin/browser/static/vendor/jStorage/jstorage.min.js
> @@ -0,0 +1,16 @@
> +(function(){function C(){var a="{}";if("userDataBehavior"==
> f){g.load("jStorage");try{a=g.getAttribute("jStorage")}catch(b){}try{r=g.
> getAttribute("jStorage_update")}catch(c){}h.jStorage=a}D();x();E()}function
> u(){var a;clearTimeout(F);F=setTimeout(function(){if("localStorage"==f||"
> globalStorage"==f)a=h.jStorage_update;else if("userDataBehavior"==f){g.
> load("jStorage");try{a=g.getAttribute("jStorage_update"
> )}catch(b){}}if(a&&a!=r){r=a;var l=p.parse(p.stringify(c.__
> jstorage_meta.CRC32)),k;C();k=p.parse(p.stringify(c.__
> jstorage_meta.CRC32));
> +var d,n=[],e=[];for(d in l)l.hasOwnProperty(d)&&(k[d]?
> l[d]!=k[d]&&"2."==String(l[d]).substr(0,2)&&n.push(d):e.push(d));for(d in
> k)k.hasOwnProperty(d)&&(l[d]||n.push(d));s(n,"updated");s(e,"deleted")}},25)}function
> s(a,b){a=[].concat(a||[]);var c,k,d,n;if("flushed"==b){a=[];for(c in
> m)m.hasOwnProperty(c)&&a.push(c);b="deleted"}c=0;for(d=a.
> length;c<d;c++){if(m[a[c]])for(k=0,n=m[a[c]].length;k<n;
> k++)m[a[c]][k](a[c],b);if(m["*"])for(k=0,n=m["*"].length;k<
> n;k++)m["*"][k](a[c],b)}}function v(){var a=(+new Date).toString();
> +if("localStorage"==f||"globalStorage"==f)try{h.
> jStorage_update=a}catch(b){f=!1}else"userDataBehavior"==f&&(
> g.setAttribute("jStorage_update",a),g.save("jStorage"));u()}function
> D(){if(h.jStorage)try{c=p.parse(String(h.jStorage))}catch(a){h.jStorage="{}"}else
> h.jStorage="{}";z=h.jStorage?String(h.jStorage).length:0;c.
> __jstorage_meta||(c.__jstorage_meta={});c.__jstorage_meta.CRC32||(c.__
> jstorage_meta.CRC32={})}function w(){if(c.__jstorage_meta.PubSub){for(var
> a=+new Date-2E3,b=0,l=c.__jstorage_meta.PubSub.length;b<
> +l;b++)if(c.__jstorage_meta.PubSub[b][0]<=a){c.__jstorage_
> meta.PubSub.splice(b,c.__jstorage_meta.PubSub.length-b)
> ;break}c.__jstorage_meta.PubSub.length||delete
> c.__jstorage_meta.PubSub}try{h.jStorage=p.stringify(c),g&&(
> g.setAttribute("jStorage",h.jStorage),g.save("jStorage")),
> z=h.jStorage?String(h.jStorage).length:0}catch(k){}}function
> q(a){if("string"!=typeof a&&"number"!=typeof a)throw new TypeError("Key
> name must be string or numeric");if("__jstorage_meta"==a)throw new
> TypeError("Reserved key name");
> +return!0}function x(){var a,b,l,k,d=Infinity,n=!1,e=[];
> clearTimeout(G);if(c.__jstorage_meta&&"object"==typeof
> c.__jstorage_meta.TTL){a=+new Date;l=c.__jstorage_meta.TTL;
> k=c.__jstorage_meta.CRC32;for(b in l)l.hasOwnProperty(b)&&(l[b]<=a?(delete
> l[b],delete k[b],delete c[b],n=!0,e.push(b)):l[b]<d&&(
> d=l[b]));Infinity!=d&&(G=setTimeout(x,Math.min(d-a,
> 2147483647)));n&&(w(),v(),s(e,"deleted"))}}function E(){var
> a;if(c.__jstorage_meta.PubSub){var b,l=A,k=[];for(a=c.__jstorage_
> meta.PubSub.length-1;0<=a;a--)b=
> +c.__jstorage_meta.PubSub[a],b[0]>A&&(l=b[0],k.unshift(b));
> for(a=k.length-1;0<=a;a--){b=k[a][1];var d=k[a][2];if(t[b])for(var
> n=0,e=t[b].length;n<e;n++)try{t[b][n](b,p.parse(p.stringify(d)))}catch(g){}}A=l}}var
> y=window.jQuery||window.$||(window.$={}),p={parse:window.
> JSON&&(window.JSON.parse||window.JSON.decode)||String.
> prototype.evalJSON&&function(a){return String(a).evalJSON()}||y.
> parseJSON||y.evalJSON,stringify:Object.toJSON||window.JSON&&(window.JSON.
> stringify||window.JSON.encode)||y.toJSON};if("function"!==
> +typeof p.parse||"function"!==typeof p.stringify)throw Error("No JSON
> support found, include //cdnjs.cloudflare.com/ajax/
> libs/json2/20110223/json2.js to page");var c={__jstorage_meta:{CRC32:{}}}
> ,h={jStorage:"{}"},g=null,z=0,f=!1,m={},F=!1,r=0,t={},A=+new
> Date,G,B={isXML:function(a){return(a=(a?a.ownerDocument||
> a:0).documentElement)?"HTML"!==a.nodeName:!1},encode:
> function(a){if(!this.isXML(a))return!1;try{return(new XMLSerializer).
> serializeToString(a)}catch(b){try{return a.xml}catch(c){}}return!1},
> +decode:function(a){var b="DOMParser"in window&&(new
> DOMParser).parseFromString||window.ActiveXObject&&function(a){var b=new
> ActiveXObject("Microsoft.XMLDOM");b.async="false";b.loadXML(a);return
> b};if(!b)return!1;a=b.call("DOMParser"in window&&new
> DOMParser||window,a,"text/xml");return this.isXML(a)?a:!1}};y.
> jStorage={version:"0.4.12",set:function(a,b,l){q(a);l=l||{};if("undefined"==typeof
> b)return this.deleteKey(a),b;if(B.isXML(b))b={_is_xml:!0,xml:B.
> encode(b)};else{if("function"==typeof b)return;
> +b&&"object"==typeof b&&(b=p.parse(p.stringify(b)))}c[a]=b;for(var
> k=c.__jstorage_meta.CRC32,d=p.stringify(b),g=d.length,e=
> 2538058380^g,h=0,f;4<=g;)f=d.charCodeAt(h)&255|(d.
> charCodeAt(++h)&255)<<8|(d.charCodeAt(++h)&255)<<16|(d.
> charCodeAt(++h)&255)<<24,f=1540483477*(f&65535)+((
> 1540483477*(f>>>16)&65535)<<16),f^=f>>>24,f=1540483477*(f&
> 65535)+((1540483477*(f>>>16)&65535)<<16),e=1540483477*(e&
> 65535)+((1540483477*(e>>>16)&65535)<<16)^f,g-=4,++h;switch(g){case
> 3:e^=(d.charCodeAt(h+2)&255)<<16;case 2:e^=
> +(d.charCodeAt(h+1)&255)<<8;case 1:e^=d.charCodeAt(h)&255,e=
> 1540483477*(e&65535)+((1540483477*(e>>>16)&65535)<<
> 16)}e^=e>>>13;e=1540483477*(e&65535)+((1540483477*(e>>>16)&
> 65535)<<16);k[a]="2."+((e^e>>>15)>>>0);this.setTTL(a,l.TTL||0);s(a,"updated");return
> b},get:function(a,b){q(a);return a in c?c[a]&&"object"==typeof
> c[a]&&c[a]._is_xml?B.decode(c[a].xml):c[a]:"undefined"==typeof
> b?null:b},deleteKey:function(a){q(a);return a in c?(delete
> c[a],"object"==typeof c.__jstorage_meta.TTL&&a in c.__jstorage_meta.TTL&&
> +delete c.__jstorage_meta.TTL[a],delete c.__jstorage_meta.CRC32[a],w()
> ,v(),s(a,"deleted"),!0):!1},setTTL:function(a,b){var l=+new
> Date;q(a);b=Number(b)||0;return a in c?(c.__jstorage_meta.TTL||(c._
> _jstorage_meta.TTL={}),0<b?c.__jstorage_meta.TTL[a]=l+b:delete
> c.__jstorage_meta.TTL[a],w(),x(),v(),!0):!1},getTTL:function(a){var
> b=+new Date;q(a);return a in c&&c.__jstorage_meta.TTL&&c.__
> jstorage_meta.TTL[a]?(a=c.__jstorage_meta.TTL[a]-b)||0:0},
> flush:function(){c={__jstorage_meta:{CRC32:{}}};w();v();s(null,
> +"flushed");return!0},storageObj:function(){function
> a(){}a.prototype=c;return new a},index:function(){var a=[],b;for(b in
> c)c.hasOwnProperty(b)&&"__jstorage_meta"!=b&&a.push(b);return
> a},storageSize:function(){return z},currentBackend:function(){return
> f},storageAvailable:function(){return!!f},listenKeyChange:
> function(a,b){q(a);m[a]||(m[a]=[]);m[a].push(b)},
> stopListening:function(a,b){q(a);if(m[a])if(b)for(var
> c=m[a].length-1;0<=c;c--)m[a][c]==b&&m[a].splice(c,1);else delete
> m[a]},subscribe:function(a,
> +b){a=(a||"").toString();if(!a)throw new TypeError("Channel not
> defined");t[a]||(t[a]=[]);t[a].push(b)},publish:function(a,
> b){a=(a||"").toString();if(!a)throw new TypeError("Channel not
> defined");c.__jstorage_meta||(c.__jstorage_meta={});c.__
> jstorage_meta.PubSub||(c.__jstorage_meta.PubSub=[]);c.__
> jstorage_meta.PubSub.unshift([+new Date,a,b]);w();v()},reInit:
> function(){C()},noConflict:function(a){delete
> window.$.jStorage;a&&(window.jStorage=this);return this}};(function(){var
> a=!1;if("localStorage"in
> +window)try{window.localStorage.setItem("_tmptest","tmpval"),a=!0,
> window.localStorage.removeItem("_tmptest")}catch(b){}if(a)try{window.
> localStorage&&(h=window.localStorage,f="localStorage",
> r=h.jStorage_update)}catch(c){}else if("globalStorage"in
> window)try{window.globalStorage&&(h="localhost"==window.location.hostname?
> window.globalStorage["localhost.localdomain"]:window.globalStorage[window.
> location.hostname],f="globalStorage",r=h.jStorage_update)}catch(k){}else
> if(g=document.createElement("link"),
> +g.addBehavior){g.style.behavior="url(#default#userData)";document.
> getElementsByTagName("head")[0].appendChild(g);try{g.load("
> jStorage")}catch(d){g.setAttribute("jStorage","{}"),
> g.save("jStorage"),g.load("jStorage")}a="{}";try{a=g.
> getAttribute("jStorage")}catch(m){}try{r=g.getAttribute("jStorage_update"
> )}catch(e){}h.jStorage=a;f="userDataBehavior"}else{g=null;
> return}D();x();"localStorage"==f||"globalStorage"==f?"addEventListener"in
> window?window.addEventListener("storage",u,!1):document.attachEvent("
> onstorage",
> +u):"userDataBehavior"==f&&setInterval(u,1E3);E();"addEventListener"in
> window&&window.addEventListener("pageshow",function(a){a.persisted&&u()},
> !1)})()})();
> \ No newline at end of file
> diff --git a/web/pgadmin/browser/templates/browser/js/browser.js
> b/web/pgadmin/browser/templates/browser/js/browser.js
> index 6b260dc8..a38ed764 100644
> --- a/web/pgadmin/browser/templates/browser/js/browser.js
> +++ b/web/pgadmin/browser/templates/browser/js/browser.js
> @@ -68,6 +68,9 @@ define(
> if (n)
> settings.url = n.generate_url(item, 'children', d, true);
> }
> + if (item != null && settings.url == url_for('browser.nodes')) {
> + settings.url = null;
> + }
> },
> loaderDelay: 100,
> show: {
> @@ -78,7 +81,8 @@ define(
> },
> view: {
> duration: 75
> - }
> + },
> + persist: 'nodes'
> });
>
> b.tree = $('#tree').aciTree('api');
> --
> 2.13.3
>
>
>

--
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

In response to

Responses

Browse pgadmin-hackers by date

  From Date Subject
Next Message Dave Page 2017-07-18 11:03:13 Re: Testing out font changes in query editor tool
Previous Message Dave Page 2017-07-18 08:44:22 Re: [pgadmin-hackers] 10k Tables and more