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 <chaoskee...@mail.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.rein...@gmail.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.co...@gmail.com'>Gary Court</a>
> +     * @see http://github.com/garycourt/murmurhash-js
> +     * @author <a href='mailto:aappl...@gmail.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

Reply via email to