You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

504 lines
14 KiB

  1. /*!
  2. * jquery.fancytree.persist.js
  3. *
  4. * Persist tree status in cookiesRemove or highlight tree nodes, based on a filter.
  5. * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
  6. *
  7. * @depends: js-cookie or jquery-cookie
  8. *
  9. * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
  10. *
  11. * Released under the MIT license
  12. * https://github.com/mar10/fancytree/wiki/LicenseInfo
  13. *
  14. * @version 2.38.3
  15. * @date 2023-02-01T20:52:50Z
  16. */
  17. (function (factory) {
  18. if (typeof define === "function" && define.amd) {
  19. // AMD. Register as an anonymous module.
  20. define(["jquery", "./jquery.fancytree"], factory);
  21. } else if (typeof module === "object" && module.exports) {
  22. // Node/CommonJS
  23. require("./jquery.fancytree");
  24. module.exports = factory(require("jquery"));
  25. } else {
  26. // Browser globals
  27. factory(jQuery);
  28. }
  29. })(function ($) {
  30. "use strict";
  31. /* global Cookies:false */
  32. /*******************************************************************************
  33. * Private functions and variables
  34. */
  35. var cookieStore = null,
  36. localStorageStore = null,
  37. sessionStorageStore = null,
  38. _assert = $.ui.fancytree.assert,
  39. ACTIVE = "active",
  40. EXPANDED = "expanded",
  41. FOCUS = "focus",
  42. SELECTED = "selected";
  43. // Accessing window.xxxStorage may raise security exceptions (see #1022)
  44. try {
  45. _assert(window.localStorage && window.localStorage.getItem);
  46. localStorageStore = {
  47. get: function (key) {
  48. return window.localStorage.getItem(key);
  49. },
  50. set: function (key, value) {
  51. window.localStorage.setItem(key, value);
  52. },
  53. remove: function (key) {
  54. window.localStorage.removeItem(key);
  55. },
  56. };
  57. } catch (e) {
  58. $.ui.fancytree.warn("Could not access window.localStorage", e);
  59. }
  60. try {
  61. _assert(window.sessionStorage && window.sessionStorage.getItem);
  62. sessionStorageStore = {
  63. get: function (key) {
  64. return window.sessionStorage.getItem(key);
  65. },
  66. set: function (key, value) {
  67. window.sessionStorage.setItem(key, value);
  68. },
  69. remove: function (key) {
  70. window.sessionStorage.removeItem(key);
  71. },
  72. };
  73. } catch (e) {
  74. $.ui.fancytree.warn("Could not access window.sessionStorage", e);
  75. }
  76. if (typeof Cookies === "function") {
  77. // Assume https://github.com/js-cookie/js-cookie
  78. cookieStore = {
  79. get: Cookies.get,
  80. set: function (key, value) {
  81. Cookies.set(key, value, this.options.persist.cookie);
  82. },
  83. remove: Cookies.remove,
  84. };
  85. } else if ($ && typeof $.cookie === "function") {
  86. // Fall back to https://github.com/carhartl/jquery-cookie
  87. cookieStore = {
  88. get: $.cookie,
  89. set: function (key, value) {
  90. $.cookie(key, value, this.options.persist.cookie);
  91. },
  92. remove: $.removeCookie,
  93. };
  94. }
  95. /* Recursively load lazy nodes
  96. * @param {string} mode 'load', 'expand', false
  97. */
  98. function _loadLazyNodes(tree, local, keyList, mode, dfd) {
  99. var i,
  100. key,
  101. l,
  102. node,
  103. foundOne = false,
  104. expandOpts = tree.options.persist.expandOpts,
  105. deferredList = [],
  106. missingKeyList = [];
  107. keyList = keyList || [];
  108. dfd = dfd || $.Deferred();
  109. for (i = 0, l = keyList.length; i < l; i++) {
  110. key = keyList[i];
  111. node = tree.getNodeByKey(key);
  112. if (node) {
  113. if (mode && node.isUndefined()) {
  114. foundOne = true;
  115. tree.debug(
  116. "_loadLazyNodes: " + node + " is lazy: loading..."
  117. );
  118. if (mode === "expand") {
  119. deferredList.push(node.setExpanded(true, expandOpts));
  120. } else {
  121. deferredList.push(node.load());
  122. }
  123. } else {
  124. tree.debug("_loadLazyNodes: " + node + " already loaded.");
  125. node.setExpanded(true, expandOpts);
  126. }
  127. } else {
  128. missingKeyList.push(key);
  129. tree.debug("_loadLazyNodes: " + node + " was not yet found.");
  130. }
  131. }
  132. $.when.apply($, deferredList).always(function () {
  133. // All lazy-expands have finished
  134. if (foundOne && missingKeyList.length > 0) {
  135. // If we read new nodes from server, try to resolve yet-missing keys
  136. _loadLazyNodes(tree, local, missingKeyList, mode, dfd);
  137. } else {
  138. if (missingKeyList.length) {
  139. tree.warn(
  140. "_loadLazyNodes: could not load those keys: ",
  141. missingKeyList
  142. );
  143. for (i = 0, l = missingKeyList.length; i < l; i++) {
  144. key = keyList[i];
  145. local._appendKey(EXPANDED, keyList[i], false);
  146. }
  147. }
  148. dfd.resolve();
  149. }
  150. });
  151. return dfd;
  152. }
  153. /**
  154. * [ext-persist] Remove persistence data of the given type(s).
  155. * Called like
  156. * $.ui.fancytree.getTree("#tree").clearCookies("active expanded focus selected");
  157. *
  158. * @alias Fancytree#clearPersistData
  159. * @requires jquery.fancytree.persist.js
  160. */
  161. $.ui.fancytree._FancytreeClass.prototype.clearPersistData = function (
  162. types
  163. ) {
  164. var local = this.ext.persist,
  165. prefix = local.cookiePrefix;
  166. types = types || "active expanded focus selected";
  167. if (types.indexOf(ACTIVE) >= 0) {
  168. local._data(prefix + ACTIVE, null);
  169. }
  170. if (types.indexOf(EXPANDED) >= 0) {
  171. local._data(prefix + EXPANDED, null);
  172. }
  173. if (types.indexOf(FOCUS) >= 0) {
  174. local._data(prefix + FOCUS, null);
  175. }
  176. if (types.indexOf(SELECTED) >= 0) {
  177. local._data(prefix + SELECTED, null);
  178. }
  179. };
  180. $.ui.fancytree._FancytreeClass.prototype.clearCookies = function (types) {
  181. this.warn(
  182. "'tree.clearCookies()' is deprecated since v2.27.0: use 'clearPersistData()' instead."
  183. );
  184. return this.clearPersistData(types);
  185. };
  186. /**
  187. * [ext-persist] Return persistence information from cookies
  188. *
  189. * Called like
  190. * $.ui.fancytree.getTree("#tree").getPersistData();
  191. *
  192. * @alias Fancytree#getPersistData
  193. * @requires jquery.fancytree.persist.js
  194. */
  195. $.ui.fancytree._FancytreeClass.prototype.getPersistData = function () {
  196. var local = this.ext.persist,
  197. prefix = local.cookiePrefix,
  198. delim = local.cookieDelimiter,
  199. res = {};
  200. res[ACTIVE] = local._data(prefix + ACTIVE);
  201. res[EXPANDED] = (local._data(prefix + EXPANDED) || "").split(delim);
  202. res[SELECTED] = (local._data(prefix + SELECTED) || "").split(delim);
  203. res[FOCUS] = local._data(prefix + FOCUS);
  204. return res;
  205. };
  206. /******************************************************************************
  207. * Extension code
  208. */
  209. $.ui.fancytree.registerExtension({
  210. name: "persist",
  211. version: "2.38.3",
  212. // Default options for this extension.
  213. options: {
  214. cookieDelimiter: "~",
  215. cookiePrefix: undefined, // 'fancytree-<treeId>-' by default
  216. cookie: {
  217. raw: false,
  218. expires: "",
  219. path: "",
  220. domain: "",
  221. secure: false,
  222. },
  223. expandLazy: false, // true: recursively expand and load lazy nodes
  224. expandOpts: undefined, // optional `opts` argument passed to setExpanded()
  225. fireActivate: true, // false: suppress `activate` event after active node was restored
  226. overrideSource: true, // true: cookie takes precedence over `source` data attributes.
  227. store: "auto", // 'cookie': force cookie, 'local': force localStore, 'session': force sessionStore
  228. types: "active expanded focus selected",
  229. },
  230. /* Generic read/write string data to cookie, sessionStorage or localStorage. */
  231. _data: function (key, value) {
  232. var store = this._local.store;
  233. if (value === undefined) {
  234. return store.get.call(this, key);
  235. } else if (value === null) {
  236. store.remove.call(this, key);
  237. } else {
  238. store.set.call(this, key, value);
  239. }
  240. },
  241. /* Append `key` to a cookie. */
  242. _appendKey: function (type, key, flag) {
  243. key = "" + key; // #90
  244. var local = this._local,
  245. instOpts = this.options.persist,
  246. delim = instOpts.cookieDelimiter,
  247. cookieName = local.cookiePrefix + type,
  248. data = local._data(cookieName),
  249. keyList = data ? data.split(delim) : [],
  250. idx = $.inArray(key, keyList);
  251. // Remove, even if we add a key, so the key is always the last entry
  252. if (idx >= 0) {
  253. keyList.splice(idx, 1);
  254. }
  255. // Append key to cookie
  256. if (flag) {
  257. keyList.push(key);
  258. }
  259. local._data(cookieName, keyList.join(delim));
  260. },
  261. treeInit: function (ctx) {
  262. var tree = ctx.tree,
  263. opts = ctx.options,
  264. local = this._local,
  265. instOpts = this.options.persist;
  266. // // For 'auto' or 'cookie' mode, the cookie plugin must be available
  267. // _assert((instOpts.store !== "auto" && instOpts.store !== "cookie") || cookieStore,
  268. // "Missing required plugin for 'persist' extension: js.cookie.js or jquery.cookie.js");
  269. local.cookiePrefix =
  270. instOpts.cookiePrefix || "fancytree-" + tree._id + "-";
  271. local.storeActive = instOpts.types.indexOf(ACTIVE) >= 0;
  272. local.storeExpanded = instOpts.types.indexOf(EXPANDED) >= 0;
  273. local.storeSelected = instOpts.types.indexOf(SELECTED) >= 0;
  274. local.storeFocus = instOpts.types.indexOf(FOCUS) >= 0;
  275. local.store = null;
  276. if (instOpts.store === "auto") {
  277. instOpts.store = localStorageStore ? "local" : "cookie";
  278. }
  279. if ($.isPlainObject(instOpts.store)) {
  280. local.store = instOpts.store;
  281. } else if (instOpts.store === "cookie") {
  282. local.store = cookieStore;
  283. } else if (instOpts.store === "local") {
  284. local.store =
  285. instOpts.store === "local"
  286. ? localStorageStore
  287. : sessionStorageStore;
  288. } else if (instOpts.store === "session") {
  289. local.store =
  290. instOpts.store === "local"
  291. ? localStorageStore
  292. : sessionStorageStore;
  293. }
  294. _assert(local.store, "Need a valid store.");
  295. // Bind init-handler to apply cookie state
  296. tree.$div.on("fancytreeinit", function (event) {
  297. if (
  298. tree._triggerTreeEvent("beforeRestore", null, {}) === false
  299. ) {
  300. return;
  301. }
  302. var cookie,
  303. dfd,
  304. i,
  305. keyList,
  306. node,
  307. prevFocus = local._data(local.cookiePrefix + FOCUS), // record this before node.setActive() overrides it;
  308. noEvents = instOpts.fireActivate === false;
  309. // tree.debug("document.cookie:", document.cookie);
  310. cookie = local._data(local.cookiePrefix + EXPANDED);
  311. keyList = cookie && cookie.split(instOpts.cookieDelimiter);
  312. if (local.storeExpanded) {
  313. // Recursively load nested lazy nodes if expandLazy is 'expand' or 'load'
  314. // Also remove expand-cookies for unmatched nodes
  315. dfd = _loadLazyNodes(
  316. tree,
  317. local,
  318. keyList,
  319. instOpts.expandLazy ? "expand" : false,
  320. null
  321. );
  322. } else {
  323. // nothing to do
  324. dfd = new $.Deferred().resolve();
  325. }
  326. dfd.done(function () {
  327. if (local.storeSelected) {
  328. cookie = local._data(local.cookiePrefix + SELECTED);
  329. if (cookie) {
  330. keyList = cookie.split(instOpts.cookieDelimiter);
  331. for (i = 0; i < keyList.length; i++) {
  332. node = tree.getNodeByKey(keyList[i]);
  333. if (node) {
  334. if (
  335. node.selected === undefined ||
  336. (instOpts.overrideSource &&
  337. node.selected === false)
  338. ) {
  339. // node.setSelected();
  340. node.selected = true;
  341. node.renderStatus();
  342. }
  343. } else {
  344. // node is no longer member of the tree: remove from cookie also
  345. local._appendKey(
  346. SELECTED,
  347. keyList[i],
  348. false
  349. );
  350. }
  351. }
  352. }
  353. // In selectMode 3 we have to fix the child nodes, since we
  354. // only stored the selected *top* nodes
  355. if (tree.options.selectMode === 3) {
  356. tree.visit(function (n) {
  357. if (n.selected) {
  358. n.fixSelection3AfterClick();
  359. return "skip";
  360. }
  361. });
  362. }
  363. }
  364. if (local.storeActive) {
  365. cookie = local._data(local.cookiePrefix + ACTIVE);
  366. if (
  367. cookie &&
  368. (opts.persist.overrideSource || !tree.activeNode)
  369. ) {
  370. node = tree.getNodeByKey(cookie);
  371. if (node) {
  372. node.debug("persist: set active", cookie);
  373. // We only want to set the focus if the container
  374. // had the keyboard focus before
  375. node.setActive(true, {
  376. noFocus: true,
  377. noEvents: noEvents,
  378. });
  379. }
  380. }
  381. }
  382. if (local.storeFocus && prevFocus) {
  383. node = tree.getNodeByKey(prevFocus);
  384. if (node) {
  385. // node.debug("persist: set focus", cookie);
  386. if (tree.options.titlesTabbable) {
  387. $(node.span).find(".fancytree-title").focus();
  388. } else {
  389. $(tree.$container).focus();
  390. }
  391. // node.setFocus();
  392. }
  393. }
  394. tree._triggerTreeEvent("restore", null, {});
  395. });
  396. });
  397. // Init the tree
  398. return this._superApply(arguments);
  399. },
  400. nodeSetActive: function (ctx, flag, callOpts) {
  401. var res,
  402. local = this._local;
  403. flag = flag !== false;
  404. res = this._superApply(arguments);
  405. if (local.storeActive) {
  406. local._data(
  407. local.cookiePrefix + ACTIVE,
  408. this.activeNode ? this.activeNode.key : null
  409. );
  410. }
  411. return res;
  412. },
  413. nodeSetExpanded: function (ctx, flag, callOpts) {
  414. var res,
  415. node = ctx.node,
  416. local = this._local;
  417. flag = flag !== false;
  418. res = this._superApply(arguments);
  419. if (local.storeExpanded) {
  420. local._appendKey(EXPANDED, node.key, flag);
  421. }
  422. return res;
  423. },
  424. nodeSetFocus: function (ctx, flag) {
  425. var res,
  426. local = this._local;
  427. flag = flag !== false;
  428. res = this._superApply(arguments);
  429. if (local.storeFocus) {
  430. local._data(
  431. local.cookiePrefix + FOCUS,
  432. this.focusNode ? this.focusNode.key : null
  433. );
  434. }
  435. return res;
  436. },
  437. nodeSetSelected: function (ctx, flag, callOpts) {
  438. var res,
  439. selNodes,
  440. tree = ctx.tree,
  441. node = ctx.node,
  442. local = this._local;
  443. flag = flag !== false;
  444. res = this._superApply(arguments);
  445. if (local.storeSelected) {
  446. if (tree.options.selectMode === 3) {
  447. // In selectMode 3 we only store the the selected *top* nodes.
  448. // De-selecting a node may also de-select some parents, so we
  449. // calculate the current status again
  450. selNodes = $.map(tree.getSelectedNodes(true), function (n) {
  451. return n.key;
  452. });
  453. selNodes = selNodes.join(
  454. ctx.options.persist.cookieDelimiter
  455. );
  456. local._data(local.cookiePrefix + SELECTED, selNodes);
  457. } else {
  458. // beforeSelect can prevent the change - flag doesn't reflect the node.selected state
  459. local._appendKey(SELECTED, node.key, node.selected);
  460. }
  461. }
  462. return res;
  463. },
  464. });
  465. // Value returned by `require('jquery.fancytree..')`
  466. return $.ui.fancytree;
  467. }); // End of closure