/*! * 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-2023, Martin Wendt (https://wwWendt.de) * * Released under the MIT license * https://github.com/mar10/fancytree/wiki/LicenseInfo * * @version @VERSION * @date @DATE */ (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 = null, sessionStorageStore = null, _assert = $.ui.fancytree.assert, ACTIVE = "active", EXPANDED = "expanded", FOCUS = "focus", SELECTED = "selected"; // Accessing window.xxxStorage may raise security exceptions (see #1022) try { _assert(window.localStorage && window.localStorage.getItem); localStorageStore = { get: function (key) { return window.localStorage.getItem(key); }, set: function (key, value) { window.localStorage.setItem(key, value); }, remove: function (key) { window.localStorage.removeItem(key); }, }; } catch (e) { $.ui.fancytree.warn("Could not access window.localStorage", e); } try { _assert(window.sessionStorage && window.sessionStorage.getItem); sessionStorageStore = { get: function (key) { return window.sessionStorage.getItem(key); }, set: function (key, value) { window.sessionStorage.setItem(key, value); }, remove: function (key) { window.sessionStorage.removeItem(key); }, }; } catch (e) { $.ui.fancytree.warn("Could not access window.sessionStorage", e); } 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(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 * $.ui.fancytree.getTree("#tree").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 * $.ui.fancytree.getTree("#tree").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: "@VERSION", // Default options for this extension. options: { cookieDelimiter: "~", cookiePrefix: undefined, // 'fancytree--' 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