UNPKG

jquery.fancytree

Version:

JavaScript tree view / tree grid plugin with support for keyboard, inline editing, filtering, checkboxes, drag'n'drop, and lazy loading

430 lines (391 loc) 13.5 kB
/*! * jquery.fancytree.persist.js * * Persist tree status in cookiesRemove or highlight tree nodes, based on a filter. * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/) * * @depends: js-cookie or jquery-cookie * * Copyright (c) 2008-2018, Martin Wendt (http://wwWendt.de) * * Released under the MIT license * https://github.com/mar10/fancytree/wiki/LicenseInfo * * @version 2.30.0 * @date 2018-09-02T15:42:49Z */ ;(function( factory ) { if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. define( [ "jquery", "./jquery.fancytree" ], factory ); } else if ( typeof module === "object" && module.exports ) { // Node/CommonJS require("./jquery.fancytree"); module.exports = factory(require("jquery")); } else { // Browser globals factory( jQuery ); } }( function( $ ) { "use strict"; /* global Cookies:false */ /******************************************************************************* * Private functions and variables */ var cookieStore = null, localStorageStore = window.localStorage ? { get: function(key){ return window.localStorage.getItem(key); }, set: function(key, value){ window.localStorage.setItem(key, value); }, remove: function(key){ window.localStorage.removeItem(key); } } : null, sessionStorageStore = window.sessionStorage ? { get: function(key){ return window.sessionStorage.getItem(key); }, set: function(key, value){ window.sessionStorage.setItem(key, value); }, remove: function(key){ window.sessionStorage.removeItem(key); } } : null, _assert = $.ui.fancytree.assert, ACTIVE = "active", EXPANDED = "expanded", FOCUS = "focus", SELECTED = "selected"; if( typeof Cookies === "function" ) { // Assume https://github.com/js-cookie/js-cookie cookieStore = { get: Cookies.get, set: function(key, value) { Cookies.set(key, value, this.options.persist.cookie); }, remove: Cookies.remove }; } else if ( $ && typeof $.cookie === "function" ) { // Fall back to https://github.com/carhartl/jquery-cookie cookieStore = { get: $.cookie, set: function(key, value) { $.cookie.set(key, value, this.options.persist.cookie); }, remove: $.removeCookie }; } /* Recursively load lazy nodes * @param {string} mode 'load', 'expand', false */ function _loadLazyNodes(tree, local, keyList, mode, dfd) { var i, key, l, node, foundOne = false, expandOpts = tree.options.persist.expandOpts, deferredList = [], missingKeyList = []; keyList = keyList || []; dfd = dfd || $.Deferred(); for( i=0, l=keyList.length; i<l; i++ ) { key = keyList[i]; node = tree.getNodeByKey(key); if( node ) { if( mode && node.isUndefined() ) { foundOne = true; tree.debug("_loadLazyNodes: " + node + " is lazy: loading..."); if( mode === "expand" ) { deferredList.push(node.setExpanded(true, expandOpts)); } else { deferredList.push(node.load()); } } else { tree.debug("_loadLazyNodes: " + node + " already loaded."); node.setExpanded(true, expandOpts); } } else { missingKeyList.push(key); tree.debug("_loadLazyNodes: " + node + " was not yet found."); } } $.when.apply($, deferredList).always(function(){ // All lazy-expands have finished if( foundOne && missingKeyList.length > 0 ) { // If we read new nodes from server, try to resolve yet-missing keys _loadLazyNodes(tree, local, missingKeyList, mode, dfd); } else { if( missingKeyList.length ) { tree.warn("_loadLazyNodes: could not load those keys: ", missingKeyList); for( i=0, l=missingKeyList.length; i<l; i++ ) { key = keyList[i]; local._appendKey(EXPANDED, keyList[i], false); } } dfd.resolve(); } }); return dfd; } /** * [ext-persist] Remove persistence data of the given type(s). * Called like * $("#tree").fancytree("getTree").clearCookies("active expanded focus selected"); * * @alias Fancytree#clearPersistData * @requires jquery.fancytree.persist.js */ $.ui.fancytree._FancytreeClass.prototype.clearPersistData = function(types){ var local = this.ext.persist, prefix = local.cookiePrefix; types = types || "active expanded focus selected"; if(types.indexOf(ACTIVE) >= 0){ local._data(prefix + ACTIVE, null); } if(types.indexOf(EXPANDED) >= 0){ local._data(prefix + EXPANDED, null); } if(types.indexOf(FOCUS) >= 0){ local._data(prefix + FOCUS, null); } if(types.indexOf(SELECTED) >= 0){ local._data(prefix + SELECTED, null); } }; $.ui.fancytree._FancytreeClass.prototype.clearCookies = function(types){ this.warn("'tree.clearCookies()' is deprecated since v2.27.0: use 'clearPersistData()' instead."); return this.clearPersistData(types); }; /** * [ext-persist] Return persistence information from cookies * * Called like * $("#tree").fancytree("getTree").getPersistData(); * * @alias Fancytree#getPersistData * @requires jquery.fancytree.persist.js */ $.ui.fancytree._FancytreeClass.prototype.getPersistData = function(){ var local = this.ext.persist, prefix = local.cookiePrefix, delim = local.cookieDelimiter, res = {}; res[ACTIVE] = local._data(prefix + ACTIVE); res[EXPANDED] = (local._data(prefix + EXPANDED) || "").split(delim); res[SELECTED] = (local._data(prefix + SELECTED) || "").split(delim); res[FOCUS] = local._data(prefix + FOCUS); return res; }; /* ***************************************************************************** * Extension code */ $.ui.fancytree.registerExtension({ name: "persist", version: "2.30.0", // Default options for this extension. options: { cookieDelimiter: "~", cookiePrefix: undefined, // 'fancytree-<treeId>-' by default cookie: { raw: false, expires: "", path: "", domain: "", secure: false }, expandLazy: false, // true: recursively expand and load lazy nodes expandOpts: undefined, // optional `opts` argument passed to setExpanded() fireActivate: true, // false: suppress `activate` event after active node was restored overrideSource: true, // true: cookie takes precedence over `source` data attributes. store: "auto", // 'cookie': force cookie, 'local': force localStore, 'session': force sessionStore types: "active expanded focus selected" }, /* Generic read/write string data to cookie, sessionStorage or localStorage. */ _data: function(key, value){ var store = this._local.store; if( value === undefined ) { return store.get.call(this, key); } else if ( value === null ) { store.remove.call(this, key); } else { store.set.call(this, key, value); } }, /* Append `key` to a cookie. */ _appendKey: function(type, key, flag){ key = "" + key; // #90 var local = this._local, instOpts = this.options.persist, delim = instOpts.cookieDelimiter, cookieName = local.cookiePrefix + type, data = local._data(cookieName), keyList = data ? data.split(delim) : [], idx = $.inArray(key, keyList); // Remove, even if we add a key, so the key is always the last entry if(idx >= 0){ keyList.splice(idx, 1); } // Append key to cookie if(flag){ keyList.push(key); } local._data(cookieName, keyList.join(delim)); }, treeInit: function(ctx){ var tree = ctx.tree, opts = ctx.options, local = this._local, instOpts = this.options.persist; // // For 'auto' or 'cookie' mode, the cookie plugin must be available // _assert((instOpts.store !== "auto" && instOpts.store !== "cookie") || cookieStore, // "Missing required plugin for 'persist' extension: js.cookie.js or jquery.cookie.js"); local.cookiePrefix = instOpts.cookiePrefix || ("fancytree-" + tree._id + "-"); local.storeActive = instOpts.types.indexOf(ACTIVE) >= 0; local.storeExpanded = instOpts.types.indexOf(EXPANDED) >= 0; local.storeSelected = instOpts.types.indexOf(SELECTED) >= 0; local.storeFocus = instOpts.types.indexOf(FOCUS) >= 0; local.store = null; if( instOpts.store === "auto" ) { instOpts.store = localStorageStore ? "local" : "cookie"; } if( $.isPlainObject(instOpts.store) ) { local.store = instOpts.store; } else if( instOpts.store === "cookie" ) { local.store = cookieStore; } else if( instOpts.store === "local" ){ local.store = (instOpts.store === "local") ? localStorageStore : sessionStorageStore; } else if( instOpts.store === "session" ){ local.store = (instOpts.store === "local") ? localStorageStore : sessionStorageStore; } _assert(local.store, "Need a valid store."); // Bind init-handler to apply cookie state tree.$div.on("fancytreeinit", function(event){ if ( tree._triggerTreeEvent("beforeRestore", null, {}) === false ) { return; } var cookie, dfd, i, keyList, node, prevFocus = local._data(local.cookiePrefix + FOCUS), // record this before node.setActive() overrides it; noEvents = instOpts.fireActivate === false; // tree.debug("document.cookie:", document.cookie); cookie = local._data(local.cookiePrefix + EXPANDED); keyList = cookie && cookie.split(instOpts.cookieDelimiter); if( local.storeExpanded ) { // Recursively load nested lazy nodes if expandLazy is 'expand' or 'load' // Also remove expand-cookies for unmatched nodes dfd = _loadLazyNodes(tree, local, keyList, instOpts.expandLazy ? "expand" : false , null); } else { // nothing to do dfd = new $.Deferred().resolve(); } dfd.done(function(){ if(local.storeSelected){ cookie = local._data(local.cookiePrefix + SELECTED); if(cookie){ keyList = cookie.split(instOpts.cookieDelimiter); for(i=0; i<keyList.length; i++){ node = tree.getNodeByKey(keyList[i]); if(node){ if(node.selected === undefined || instOpts.overrideSource && (node.selected === false)){ // node.setSelected(); node.selected = true; node.renderStatus(); } }else{ // node is no longer member of the tree: remove from cookie also local._appendKey(SELECTED, keyList[i], false); } } } // In selectMode 3 we have to fix the child nodes, since we // only stored the selected *top* nodes if( tree.options.selectMode === 3 ){ tree.visit(function(n){ if( n.selected ) { n.fixSelection3AfterClick(); return "skip"; } }); } } if(local.storeActive){ cookie = local._data(local.cookiePrefix + ACTIVE); if(cookie && (opts.persist.overrideSource || !tree.activeNode)){ node = tree.getNodeByKey(cookie); if(node){ node.debug("persist: set active", cookie); // We only want to set the focus if the container // had the keyboard focus before node.setActive(true, { noFocus: true, noEvents: noEvents }); } } } if(local.storeFocus && prevFocus){ node = tree.getNodeByKey(prevFocus); if(node){ // node.debug("persist: set focus", cookie); if( tree.options.titlesTabbable ) { $(node.span).find(".fancytree-title").focus(); } else { $(tree.$container).focus(); } // node.setFocus(); } } tree._triggerTreeEvent("restore", null, {}); }); }); // Init the tree return this._superApply(arguments); }, nodeSetActive: function(ctx, flag, callOpts) { var res, local = this._local; flag = (flag !== false); res = this._superApply(arguments); if(local.storeActive){ local._data(local.cookiePrefix + ACTIVE, this.activeNode ? this.activeNode.key : null); } return res; }, nodeSetExpanded: function(ctx, flag, callOpts) { var res, node = ctx.node, local = this._local; flag = (flag !== false); res = this._superApply(arguments); if(local.storeExpanded){ local._appendKey(EXPANDED, node.key, flag); } return res; }, nodeSetFocus: function(ctx, flag) { var res, local = this._local; flag = (flag !== false); res = this._superApply(arguments); if( local.storeFocus ) { local._data(local.cookiePrefix + FOCUS, this.focusNode ? this.focusNode.key : null); } return res; }, nodeSetSelected: function(ctx, flag, callOpts) { var res, selNodes, tree = ctx.tree, node = ctx.node, local = this._local; flag = (flag !== false); res = this._superApply(arguments); if(local.storeSelected){ if( tree.options.selectMode === 3 ){ // In selectMode 3 we only store the the selected *top* nodes. // De-selecting a node may also de-select some parents, so we // calculate the current status again selNodes = $.map(tree.getSelectedNodes(true), function(n){ return n.key; }); selNodes = selNodes.join(ctx.options.persist.cookieDelimiter); local._data(local.cookiePrefix + SELECTED, selNodes); } else { // beforeSelect can prevent the change - flag doesn't reflect the node.selected state local._appendKey(SELECTED, node.key, node.selected); } } return res; } }); // Value returned by `require('jquery.fancytree..')` return $.ui.fancytree; })); // End of closure