1
0
Fork 0

Adding indexmenu version 2024-01-05 (ed06f21).

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2024-12-01 20:38:26 +01:00
parent 92cc8375f2
commit f339727d60
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
766 changed files with 83299 additions and 0 deletions

View file

@ -0,0 +1,714 @@
/*!
* jquery.fancytree.ariagrid.js
*
* Support ARIA compliant markup and keyboard navigation for tree grids with
* embedded input controls.
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
*
* @requires ext-table
*
* Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
*
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.38.3
* @date 2023-02-01T20:52:50Z
*/
(function (factory) {
if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
define([
"jquery",
"./jquery.fancytree",
"./jquery.fancytree.table",
], factory);
} else if (typeof module === "object" && module.exports) {
// Node/CommonJS
require("./jquery.fancytree.table"); // core + table
module.exports = factory(require("jquery"));
} else {
// Browser globals
factory(jQuery);
}
})(function ($) {
"use strict";
/*******************************************************************************
* Private functions and variables
*/
// Allow these navigation keys even when input controls are focused
var FT = $.ui.fancytree,
clsFancytreeActiveCell = "fancytree-active-cell",
clsFancytreeCellMode = "fancytree-cell-mode",
clsFancytreeCellNavMode = "fancytree-cell-nav-mode",
VALID_MODES = ["allow", "force", "start", "off"],
// Define which keys are handled by embedded <input> control, and should
// *not* be passed to tree navigation handler in cell-edit mode:
INPUT_KEYS = {
text: ["left", "right", "home", "end", "backspace"],
number: ["up", "down", "left", "right", "home", "end", "backspace"],
checkbox: [],
link: [],
radiobutton: ["up", "down"],
"select-one": ["up", "down"],
"select-multiple": ["up", "down"],
},
NAV_KEYS = ["up", "down", "left", "right", "home", "end"];
/* Set aria-activedescendant on container to active cell's ID (generate one if required).*/
function setActiveDescendant(tree, $target) {
var id = $target ? $target.uniqueId().attr("id") : "";
tree.$container.attr("aria-activedescendant", id);
}
/* Calculate TD column index (considering colspans).*/
function getColIdx($tr, $td) {
var colspan,
td = $td.get(0),
idx = 0;
$tr.children().each(function () {
if (this === td) {
return false;
}
colspan = $(this).prop("colspan");
idx += colspan ? colspan : 1;
});
return idx;
}
/* Find TD at given column index (considering colspans).*/
function findTdAtColIdx($tr, colIdx) {
var colspan,
res = null,
idx = 0;
$tr.children().each(function () {
if (idx >= colIdx) {
res = $(this);
return false;
}
colspan = $(this).prop("colspan");
idx += colspan ? colspan : 1;
});
return res;
}
/* Find adjacent cell for a given direction. Skip empty cells and consider merged cells */
function findNeighbourTd(tree, $target, keyCode) {
var nextNode,
node,
navMap = { "ctrl+home": "first", "ctrl+end": "last" },
$td = $target.closest("td"),
$tr = $td.parent(),
treeOpts = tree.options,
colIdx = getColIdx($tr, $td),
$tdNext = null;
keyCode = navMap[keyCode] || keyCode;
switch (keyCode) {
case "left":
$tdNext = treeOpts.rtl ? $td.next() : $td.prev();
break;
case "right":
$tdNext = treeOpts.rtl ? $td.prev() : $td.next();
break;
case "up":
case "down":
case "ctrl+home":
case "ctrl+end":
node = $tr[0].ftnode;
nextNode = tree.findRelatedNode(node, keyCode);
if (nextNode) {
nextNode.makeVisible();
nextNode.setActive();
$tdNext = findTdAtColIdx($(nextNode.tr), colIdx);
}
break;
case "home":
$tdNext = treeOpts.rtl
? $tr.children("td").last()
: $tr.children("td").first();
break;
case "end":
$tdNext = treeOpts.rtl
? $tr.children("td").first()
: $tr.children("td").last();
break;
}
return $tdNext && $tdNext.length ? $tdNext : null;
}
/* Return a descriptive string of the current mode. */
function getGridNavMode(tree) {
if (tree.$activeTd) {
return tree.forceNavMode ? "cell-nav" : "cell-edit";
}
return "row";
}
/* .*/
function activateEmbeddedLink($td) {
// $td.find( "a" )[ 0 ].trigger("click"); // does not work (always)?
// $td.find( "a" ).trigger("click");
var event = document.createEvent("MouseEvent"),
a = $td.find("a")[0]; // document.getElementById('nameOfID');
event = new CustomEvent("click");
a.dispatchEvent(event);
}
/**
* [ext-ariagrid] Set active cell and activate cell-nav or cell-edit mode if needed.
* Pass $td=null to enter row-mode.
*
* See also FancytreeNode#setActive(flag, {cell: idx})
*
* @param {jQuery | Element | integer} [$td]
* @param {Event|null} [orgEvent=null]
* @alias Fancytree#activateCell
* @requires jquery.fancytree.ariagrid.js
* @since 2.23
*/
$.ui.fancytree._FancytreeClass.prototype.activateCell = function (
$td,
orgEvent
) {
var colIdx,
$input,
$tr,
res,
tree = this,
$prevTd = this.$activeTd || null,
newNode = $td ? FT.getNode($td) : null,
prevNode = $prevTd ? FT.getNode($prevTd) : null,
anyNode = newNode || prevNode,
$prevTr = $prevTd ? $prevTd.closest("tr") : null;
anyNode.debug(
"activateCell(" +
($prevTd ? $prevTd.text() : "null") +
") -> " +
($td ? $td.text() : "OFF")
);
// Make available as event
if ($td) {
FT.assert($td.length, "Invalid active cell");
colIdx = getColIdx($(newNode.tr), $td);
res = this._triggerNodeEvent("activateCell", newNode, orgEvent, {
activeTd: tree.$activeTd,
colIdx: colIdx,
mode: null, // editMode ? "cell-edit" : "cell-nav"
});
if (res === false) {
return false;
}
this.$container.addClass(clsFancytreeCellMode);
this.$container.toggleClass(
clsFancytreeCellNavMode,
!!this.forceNavMode
);
$tr = $td.closest("tr");
if ($prevTd) {
// cell-mode => cell-mode
if ($prevTd.is($td)) {
return;
}
$prevTd
.removeAttr("tabindex")
.removeClass(clsFancytreeActiveCell);
if (!$prevTr.is($tr)) {
// We are moving to a different row: only the inputs in the
// active row should be tabbable
$prevTr.find(">td :input,a").attr("tabindex", "-1");
}
}
$tr.find(">td :input:enabled,a").attr("tabindex", "0");
newNode.setActive();
$td.addClass(clsFancytreeActiveCell);
this.$activeTd = $td;
$input = $td.find(":input:enabled,a");
this.debug("Focus input", $input);
if ($input.length) {
$input.focus();
setActiveDescendant(this, $input);
} else {
$td.attr("tabindex", "-1").focus();
setActiveDescendant(this, $td);
}
} else {
res = this._triggerNodeEvent("activateCell", prevNode, orgEvent, {
activeTd: null,
colIdx: null,
mode: "row",
});
if (res === false) {
return false;
}
// $td == null: switch back to row-mode
this.$container.removeClass(
clsFancytreeCellMode + " " + clsFancytreeCellNavMode
);
// console.log("activateCell: set row-mode for " + this.activeNode, $prevTd);
if ($prevTd) {
// cell-mode => row-mode
$prevTd
.removeAttr("tabindex")
.removeClass(clsFancytreeActiveCell);
// In row-mode, only embedded inputs of the active row are tabbable
$prevTr
.find("td")
.blur() // we need to blur first, because otherwise the focus frame is not reliably removed(?)
.removeAttr("tabindex");
$prevTr.find(">td :input,a").attr("tabindex", "-1");
this.$activeTd = null;
// The cell lost focus, but the tree still needs to capture keys:
this.activeNode.setFocus();
setActiveDescendant(this, $tr);
} else {
// row-mode => row-mode (nothing to do)
}
}
};
/*******************************************************************************
* Extension code
*/
$.ui.fancytree.registerExtension({
name: "ariagrid",
version: "2.38.3",
// Default options for this extension.
options: {
// Internal behavior flags
activateCellOnDoubelclick: true,
cellFocus: "allow",
// TODO: use a global tree option `name` or `title` instead?:
label: "Tree Grid", // Added as `aria-label` attribute
},
treeInit: function (ctx) {
var tree = ctx.tree,
treeOpts = ctx.options,
opts = treeOpts.ariagrid;
// ariagrid requires the table extension to be loaded before itself
if (tree.ext.grid) {
this._requireExtension("grid", true, true);
} else {
this._requireExtension("table", true, true);
}
if (!treeOpts.aria) {
$.error("ext-ariagrid requires `aria: true`");
}
if ($.inArray(opts.cellFocus, VALID_MODES) < 0) {
$.error("Invalid `cellFocus` option");
}
this._superApply(arguments);
// The combination of $activeTd and forceNavMode determines the current
// navigation mode:
this.$activeTd = null; // active cell (null in row-mode)
this.forceNavMode = true;
this.$container
.addClass("fancytree-ext-ariagrid")
.toggleClass(clsFancytreeCellNavMode, !!this.forceNavMode)
.attr("aria-label", "" + opts.label);
this.$container
.find("thead > tr > th")
.attr("role", "columnheader");
// Store table options for easier evaluation of default actions
// depending of active cell column
this.nodeColumnIdx = treeOpts.table.nodeColumnIdx;
this.checkboxColumnIdx = treeOpts.table.checkboxColumnIdx;
if (this.checkboxColumnIdx == null) {
this.checkboxColumnIdx = this.nodeColumnIdx;
}
this.$container
.on("focusin", function (event) {
// Activate node if embedded input gets focus (due to a click)
var node = FT.getNode(event.target),
$td = $(event.target).closest("td");
// tree.debug( "focusin: " + ( node ? node.title : "null" ) +
// ", target: " + ( $td ? $td.text() : null ) +
// ", node was active: " + ( node && node.isActive() ) +
// ", last cell: " + ( tree.$activeTd ? tree.$activeTd.text() : null ) );
// tree.debug( "focusin: target", event.target );
// TODO: add ":input" as delegate filter instead of testing here
if (
node &&
!$td.is(tree.$activeTd) &&
$(event.target).is(":input")
) {
node.debug("Activate cell on INPUT focus event");
tree.activateCell($td);
}
})
.on("fancytreeinit", function (event, data) {
if (
opts.cellFocus === "start" ||
opts.cellFocus === "force"
) {
tree.debug("Enforce cell-mode on init");
tree.debug(
"init",
tree.getActiveNode() || tree.getFirstChild()
);
(
tree.getActiveNode() || tree.getFirstChild()
).setActive(true, { cell: tree.nodeColumnIdx });
tree.debug(
"init2",
tree.getActiveNode() || tree.getFirstChild()
);
}
})
.on("fancytreefocustree", function (event, data) {
// Enforce cell-mode when container gets focus
if (opts.cellFocus === "force" && !tree.$activeTd) {
var node = tree.getActiveNode() || tree.getFirstChild();
tree.debug("Enforce cell-mode on focusTree event");
node.setActive(true, { cell: 0 });
}
})
// .on("fancytreeupdateviewport", function(event, data) {
// tree.debug(event.type, data);
// })
.on("fancytreebeforeupdateviewport", function (event, data) {
// When scrolling, the TR may be re-used by another node, so the
// active cell marker an
// tree.debug(event.type, data);
if (tree.viewport && tree.$activeTd) {
tree.info("Cancel cell-mode due to scroll event.");
tree.activateCell(null);
}
});
},
nodeClick: function (ctx) {
var targetType = ctx.targetType,
tree = ctx.tree,
node = ctx.node,
event = ctx.originalEvent,
$target = $(event.target),
$td = $target.closest("td");
tree.debug(
"nodeClick: node: " +
(node ? node.title : "null") +
", targetType: " +
targetType +
", target: " +
($td.length ? $td.text() : null) +
", node was active: " +
(node && node.isActive()) +
", last cell: " +
(tree.$activeTd ? tree.$activeTd.text() : null)
);
if (tree.$activeTd) {
// If already in cell-mode, activate new cell
tree.activateCell($td);
if ($target.is(":input")) {
return;
} else if (
$target.is(".fancytree-checkbox") ||
$target.is(".fancytree-expander")
) {
return this._superApply(arguments);
}
return false;
}
return this._superApply(arguments);
},
nodeDblclick: function (ctx) {
var tree = ctx.tree,
treeOpts = ctx.options,
opts = treeOpts.ariagrid,
event = ctx.originalEvent,
$td = $(event.target).closest("td");
// console.log("nodeDblclick", tree.$activeTd, ctx.options.ariagrid.cellFocus)
if (
opts.activateCellOnDoubelclick &&
!tree.$activeTd &&
opts.cellFocus === "allow"
) {
// If in row-mode, activate new cell
tree.activateCell($td);
return false;
}
return this._superApply(arguments);
},
nodeRenderStatus: function (ctx) {
// Set classes for current status
var res,
node = ctx.node,
$tr = $(node.tr);
res = this._super(ctx);
if (node.parent) {
$tr.attr("aria-level", node.getLevel())
.attr("aria-setsize", node.parent.children.length)
.attr("aria-posinset", node.getIndex() + 1);
// 2018-06-24: not required according to
// https://github.com/w3c/aria-practices/issues/132#issuecomment-397698250
// if ( $tr.is( ":hidden" ) ) {
// $tr.attr( "aria-hidden", true );
// } else {
// $tr.removeAttr( "aria-hidden" );
// }
// this.debug("nodeRenderStatus: " + this.$activeTd + ", " + $tr.attr("aria-expanded"));
// In cell-mode, move aria-expanded attribute from TR to first child TD
if (this.$activeTd && $tr.attr("aria-expanded") != null) {
$tr.remove("aria-expanded");
$tr.find("td")
.eq(this.nodeColumnIdx)
.attr("aria-expanded", node.isExpanded());
} else {
$tr.find("td")
.eq(this.nodeColumnIdx)
.removeAttr("aria-expanded");
}
}
return res;
},
nodeSetActive: function (ctx, flag, callOpts) {
var $td,
node = ctx.node,
tree = ctx.tree,
$tr = $(node.tr);
flag = flag !== false;
node.debug("nodeSetActive(" + flag + ")", callOpts);
// Support custom `cell` option
if (flag && callOpts && callOpts.cell != null) {
// `cell` may be a col-index, <td>, or `$(td)`
if (typeof callOpts.cell === "number") {
$td = findTdAtColIdx($tr, callOpts.cell);
} else {
$td = $(callOpts.cell);
}
tree.activateCell($td);
return;
}
// tree.debug( "nodeSetActive: activeNode " + this.activeNode );
return this._superApply(arguments);
},
nodeKeydown: function (ctx) {
var handleKeys,
inputType,
res,
$td,
$embeddedCheckbox = null,
tree = ctx.tree,
node = ctx.node,
treeOpts = ctx.options,
opts = treeOpts.ariagrid,
event = ctx.originalEvent,
eventString = FT.eventToString(event),
$target = $(event.target),
$activeTd = this.$activeTd,
$activeTr = $activeTd ? $activeTd.closest("tr") : null,
colIdx = $activeTd ? getColIdx($activeTr, $activeTd) : -1,
forceNav =
$activeTd &&
tree.forceNavMode &&
$.inArray(eventString, NAV_KEYS) >= 0;
if (opts.cellFocus === "off") {
return this._superApply(arguments);
}
if ($target.is(":input:enabled")) {
inputType = $target.prop("type");
} else if ($target.is("a")) {
inputType = "link";
}
if ($activeTd && $activeTd.find(":checkbox:enabled").length === 1) {
$embeddedCheckbox = $activeTd.find(":checkbox:enabled");
inputType = "checkbox";
}
tree.debug(
"nodeKeydown(" +
eventString +
"), activeTd: '" +
($activeTd && $activeTd.text()) +
"', inputType: " +
inputType
);
if (inputType && eventString !== "esc" && !forceNav) {
handleKeys = INPUT_KEYS[inputType];
if (handleKeys && $.inArray(eventString, handleKeys) >= 0) {
return; // Let input control handle the key
}
}
switch (eventString) {
case "right":
if ($activeTd) {
// Cell mode: move to neighbour (stop on right border)
$td = findNeighbourTd(tree, $activeTd, eventString);
if ($td) {
tree.activateCell($td);
}
} else if (
node &&
!node.isExpanded() &&
node.hasChildren() !== false
) {
// Row mode and current node can be expanded:
// default handling will expand.
break;
} else {
// Row mode: switch to cell-mode
$td = $(node.tr).find(">td").first();
tree.activateCell($td);
}
return false; // no default handling
case "left":
case "home":
case "end":
case "ctrl+home":
case "ctrl+end":
case "up":
case "down":
if ($activeTd) {
// Cell mode: move to neighbour
$td = findNeighbourTd(tree, $activeTd, eventString);
// Note: $td may be null if we move outside bounds. In this case
// we switch back to row-mode (i.e. call activateCell(null) ).
if (!$td && "left right".indexOf(eventString) < 0) {
// Only switch to row-mode if left/right hits the bounds
return false;
}
if ($td || opts.cellFocus !== "force") {
tree.activateCell($td);
}
return false;
}
break;
case "esc":
if ($activeTd && !tree.forceNavMode) {
// Switch from cell-edit-mode to cell-nav-mode
// $target.closest( "td" ).focus();
tree.forceNavMode = true;
tree.debug("Enter cell-nav-mode");
tree.$container.toggleClass(
clsFancytreeCellNavMode,
!!tree.forceNavMode
);
return false;
} else if ($activeTd && opts.cellFocus !== "force") {
// Switch back from cell-mode to row-mode
tree.activateCell(null);
return false;
}
// tree.$container.toggleClass( clsFancytreeCellNavMode, !!tree.forceNavMode );
break;
case "return":
// Let user override the default action.
// This event is triggered in row-mode and cell-mode
res = tree._triggerNodeEvent(
"defaultGridAction",
node,
event,
{
activeTd: tree.$activeTd ? tree.$activeTd[0] : null,
colIdx: colIdx,
mode: getGridNavMode(tree),
}
);
if (res === false) {
return false;
}
// Implement default actions (for cell-mode only).
if ($activeTd) {
// Apply 'default action' for embedded cell control
if (colIdx === this.nodeColumnIdx) {
node.toggleExpanded();
} else if (colIdx === this.checkboxColumnIdx) {
// TODO: only in checkbox mode!
node.toggleSelected();
} else if ($embeddedCheckbox) {
// Embedded checkboxes are always toggled (ignoring `autoFocusInput`)
$embeddedCheckbox.prop(
"checked",
!$embeddedCheckbox.prop("checked")
);
} else if (tree.forceNavMode && $target.is(":input")) {
tree.forceNavMode = false;
tree.$container.removeClass(
clsFancytreeCellNavMode
);
tree.debug("enable cell-edit-mode");
} else if ($activeTd.find("a").length === 1) {
activateEmbeddedLink($activeTd);
}
} else {
// ENTER in row-mode: Switch from row-mode to cell-mode
// TODO: it was also suggested to expand/collapse instead
// https://github.com/w3c/aria-practices/issues/132#issuecomment-407634891
$td = $(node.tr).find(">td").nth(this.nodeColumnIdx);
tree.activateCell($td);
}
return false; // no default handling
case "space":
if ($activeTd) {
if (colIdx === this.checkboxColumnIdx) {
node.toggleSelected();
} else if ($embeddedCheckbox) {
$embeddedCheckbox.prop(
"checked",
!$embeddedCheckbox.prop("checked")
);
}
return false; // no default handling
}
break;
default:
// Allow to focus input by typing alphanum keys
}
return this._superApply(arguments);
},
treeSetOption: function (ctx, key, value) {
var tree = ctx.tree,
opts = tree.options.ariagrid;
if (key === "ariagrid") {
// User called `$().fancytree("option", "ariagrid.SUBKEY", VALUE)`
if (value.cellFocus !== opts.cellFocus) {
if ($.inArray(value.cellFocus, VALID_MODES) < 0) {
$.error("Invalid `cellFocus` option");
}
// TODO: fix current focus and mode
}
}
return this._superApply(arguments);
},
});
// Value returned by `require('jquery.fancytree..')`
return $.ui.fancytree;
}); // End of closure

View file

@ -0,0 +1,241 @@
// Extending Fancytree
// ===================
//
// See also the [live demo](https://wwWendt.de/tech/fancytree/demo/sample-ext-childcounter.html) of this code.
//
// Every extension should have a comment header containing some information
// about the author, copyright and licensing. Also a pointer to the latest
// source code.
// Prefix with `/*!` so the comment is not removed by the minifier.
/*!
* jquery.fancytree.childcounter.js
*
* Add a child counter bubble to tree nodes.
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
*
* Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
*
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.38.3
* @date 2023-02-01T20:52:50Z
*/
// To keep the global namespace clean, we wrap everything in a closure.
// The UMD wrapper pattern defines the dependencies on jQuery and the
// Fancytree core module, and makes sure that we can use the `require()`
// syntax with package loaders.
(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 ($) {
// Consider to use [strict mode](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
"use strict";
// The [coding guidelines](http://contribute.jquery.org/style-guide/js/)
// require jshint /eslint compliance.
// But for this sample, we want to allow unused variables for demonstration purpose.
/*eslint-disable no-unused-vars */
// Adding methods
// --------------
// New member functions can be added to the `Fancytree` class.
// This function will be available for every tree instance:
//
// var tree = $.ui.fancytree.getTree("#tree");
// tree.countSelected(false);
$.ui.fancytree._FancytreeClass.prototype.countSelected = function (
topOnly
) {
var tree = this,
treeOptions = tree.options;
return tree.getSelectedNodes(topOnly).length;
};
// The `FancytreeNode` class can also be easily extended. This would be called
// like
// node.updateCounters();
//
// It is also good practice to add a docstring comment.
/**
* [ext-childcounter] Update counter badges for `node` and its parents.
* May be called in the `loadChildren` event, to update parents of lazy loaded
* nodes.
* @alias FancytreeNode#updateCounters
* @requires jquery.fancytree.childcounters.js
*/
$.ui.fancytree._FancytreeNodeClass.prototype.updateCounters = function () {
var node = this,
$badge = $("span.fancytree-childcounter", node.span),
extOpts = node.tree.options.childcounter,
count = node.countChildren(extOpts.deep);
node.data.childCounter = count;
if (
(count || !extOpts.hideZeros) &&
(!node.isExpanded() || !extOpts.hideExpanded)
) {
if (!$badge.length) {
$badge = $("<span class='fancytree-childcounter'/>").appendTo(
$(
"span.fancytree-icon,span.fancytree-custom-icon",
node.span
)
);
}
$badge.text(count);
} else {
$badge.remove();
}
if (extOpts.deep && !node.isTopLevel() && !node.isRootNode()) {
node.parent.updateCounters();
}
};
// Finally, we can extend the widget API and create functions that are called
// like so:
//
// $("#tree").fancytree("widgetMethod1", "abc");
$.ui.fancytree.prototype.widgetMethod1 = function (arg1) {
var tree = this.tree;
return arg1;
};
// Register a Fancytree extension
// ------------------------------
// A full blown extension, extension is available for all trees and can be
// enabled like so (see also the [live demo](https://wwWendt.de/tech/fancytree/demo/sample-ext-childcounter.html)):
//
// <script src="../src/jquery.fancytree.js"></script>
// <script src="../src/jquery.fancytree.childcounter.js"></script>
// ...
//
// $("#tree").fancytree({
// extensions: ["childcounter"],
// childcounter: {
// hideExpanded: true
// },
// ...
// });
//
/* 'childcounter' extension */
$.ui.fancytree.registerExtension({
// Every extension must be registered by a unique name.
name: "childcounter",
// Version information should be compliant with [semver](http://semver.org)
version: "2.38.3",
// Extension specific options and their defaults.
// This options will be available as `tree.options.childcounter.hideExpanded`
options: {
deep: true,
hideZeros: true,
hideExpanded: false,
},
// Attributes other than `options` (or functions) can be defined here, and
// will be added to the tree.ext.EXTNAME namespace, in this case `tree.ext.childcounter.foo`.
// They can also be accessed as `this._local.foo` from within the extension
// methods.
foo: 42,
// Local functions are prefixed with an underscore '_'.
// Callable as `this._local._appendCounter()`.
_appendCounter: function (bar) {
var tree = this;
},
// **Override virtual methods for this extension.**
//
// Fancytree implements a number of 'hook methods', prefixed by 'node...' or 'tree...'.
// with a `ctx` argument (see [EventData](https://wwWendt.de/tech/fancytree/doc/jsdoc/global.html#EventData)
// for details) and an extended calling context:<br>
// `this` : the Fancytree instance<br>
// `this._local`: the namespace that contains extension attributes and private methods (same as this.ext.EXTNAME)<br>
// `this._super`: the virtual function that was overridden (member of previous extension or Fancytree)
//
// See also the [complete list of available hook functions](https://wwWendt.de/tech/fancytree/doc/jsdoc/Fancytree_Hooks.html).
/* Init */
// `treeInit` is triggered when a tree is initalized. We can set up classes or
// bind event handlers here...
treeInit: function (ctx) {
var tree = this, // same as ctx.tree,
opts = ctx.options,
extOpts = ctx.options.childcounter;
// Optionally check for dependencies with other extensions
/* this._requireExtension("glyph", false, false); */
// Call the base implementation
this._superApply(arguments);
// Add a class to the tree container
this.$container.addClass("fancytree-ext-childcounter");
},
// Destroy this tree instance (we only call the default implementation, so
// this method could as well be omitted).
treeDestroy: function (ctx) {
this._superApply(arguments);
},
// Overload the `renderTitle` hook, to append a counter badge
nodeRenderTitle: function (ctx, title) {
var node = ctx.node,
extOpts = ctx.options.childcounter,
count =
node.data.childCounter == null
? node.countChildren(extOpts.deep)
: +node.data.childCounter;
// Let the base implementation render the title
// We use `_super()` instead of `_superApply()` here, since it is a little bit
// more performant when called often
this._super(ctx, title);
// Append a counter badge
if (
(count || !extOpts.hideZeros) &&
(!node.isExpanded() || !extOpts.hideExpanded)
) {
$(
"span.fancytree-icon,span.fancytree-custom-icon",
node.span
).append(
$("<span class='fancytree-childcounter'/>").text(count)
);
}
},
// Overload the `setExpanded` hook, so the counters are updated
nodeSetExpanded: function (ctx, flag, callOpts) {
var tree = ctx.tree,
node = ctx.node;
// Let the base implementation expand/collapse the node, then redraw the title
// after the animation has finished
return this._superApply(arguments).always(function () {
tree.nodeRenderTitle(ctx);
});
},
// End of extension definition
});
// Value returned by `require('jquery.fancytree..')`
return $.ui.fancytree;
}); // End of closure

View file

@ -0,0 +1,514 @@
/*!
*
* jquery.fancytree.clones.js
* Support faster lookup of nodes by key and shared ref-ids.
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
*
* Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
*
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.38.3
* @date 2023-02-01T20:52:50Z
*/
(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";
/*******************************************************************************
* Private functions and variables
*/
var _assert = $.ui.fancytree.assert;
/* Return first occurrence of member from array. */
function _removeArrayMember(arr, elem) {
// TODO: use Array.indexOf for IE >= 9
var i;
for (i = arr.length - 1; i >= 0; i--) {
if (arr[i] === elem) {
arr.splice(i, 1);
return true;
}
}
return false;
}
/**
* JS Implementation of MurmurHash3 (r136) (as of May 20, 2011)
*
* @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
* @see http://github.com/garycourt/murmurhash-js
* @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a>
* @see http://sites.google.com/site/murmurhash/
*
* @param {string} key ASCII only
* @param {boolean} [asString=false]
* @param {number} seed Positive integer only
* @return {number} 32-bit positive integer hash
*/
function hashMurmur3(key, asString, seed) {
/*eslint-disable no-bitwise */
var h1b,
k1,
remainder = key.length & 3,
bytes = key.length - remainder,
h1 = seed,
c1 = 0xcc9e2d51,
c2 = 0x1b873593,
i = 0;
while (i < bytes) {
k1 =
(key.charCodeAt(i) & 0xff) |
((key.charCodeAt(++i) & 0xff) << 8) |
((key.charCodeAt(++i) & 0xff) << 16) |
((key.charCodeAt(++i) & 0xff) << 24);
++i;
k1 =
((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) &
0xffffffff;
k1 = (k1 << 15) | (k1 >>> 17);
k1 =
((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) &
0xffffffff;
h1 ^= k1;
h1 = (h1 << 13) | (h1 >>> 19);
h1b =
((h1 & 0xffff) * 5 + ((((h1 >>> 16) * 5) & 0xffff) << 16)) &
0xffffffff;
h1 =
(h1b & 0xffff) +
0x6b64 +
((((h1b >>> 16) + 0xe654) & 0xffff) << 16);
}
k1 = 0;
switch (remainder) {
case 3:
k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
// fall through
case 2:
k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
// fall through
case 1:
k1 ^= key.charCodeAt(i) & 0xff;
k1 =
((k1 & 0xffff) * c1 +
((((k1 >>> 16) * c1) & 0xffff) << 16)) &
0xffffffff;
k1 = (k1 << 15) | (k1 >>> 17);
k1 =
((k1 & 0xffff) * c2 +
((((k1 >>> 16) * c2) & 0xffff) << 16)) &
0xffffffff;
h1 ^= k1;
}
h1 ^= key.length;
h1 ^= h1 >>> 16;
h1 =
((h1 & 0xffff) * 0x85ebca6b +
((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) &
0xffffffff;
h1 ^= h1 >>> 13;
h1 =
((h1 & 0xffff) * 0xc2b2ae35 +
((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16)) &
0xffffffff;
h1 ^= h1 >>> 16;
if (asString) {
// Convert to 8 digit hex string
return ("0000000" + (h1 >>> 0).toString(16)).substr(-8);
}
return h1 >>> 0;
/*eslint-enable no-bitwise */
}
/*
* Return a unique key for node by calculating the hash of the parents refKey-list.
*/
function calcUniqueKey(node) {
var key,
h1,
path = $.map(node.getParentList(false, true), function (e) {
return e.refKey || e.key;
});
path = path.join("/");
// 32-bit has a high probability of collisions, so we pump up to 64-bit
// https://security.stackexchange.com/q/209882/207588
h1 = hashMurmur3(path, true);
key = "id_" + h1 + hashMurmur3(h1 + path, true);
return key;
}
/**
* [ext-clones] Return a list of clone-nodes (i.e. same refKey) or null.
* @param {boolean} [includeSelf=false]
* @returns {FancytreeNode[] | null}
*
* @alias FancytreeNode#getCloneList
* @requires jquery.fancytree.clones.js
*/
$.ui.fancytree._FancytreeNodeClass.prototype.getCloneList = function (
includeSelf
) {
var key,
tree = this.tree,
refList = tree.refMap[this.refKey] || null,
keyMap = tree.keyMap;
if (refList) {
key = this.key;
// Convert key list to node list
if (includeSelf) {
refList = $.map(refList, function (val) {
return keyMap[val];
});
} else {
refList = $.map(refList, function (val) {
return val === key ? null : keyMap[val];
});
if (refList.length < 1) {
refList = null;
}
}
}
return refList;
};
/**
* [ext-clones] Return true if this node has at least another clone with same refKey.
* @returns {boolean}
*
* @alias FancytreeNode#isClone
* @requires jquery.fancytree.clones.js
*/
$.ui.fancytree._FancytreeNodeClass.prototype.isClone = function () {
var refKey = this.refKey || null,
refList = (refKey && this.tree.refMap[refKey]) || null;
return !!(refList && refList.length > 1);
};
/**
* [ext-clones] Update key and/or refKey for an existing node.
* @param {string} key
* @param {string} refKey
* @returns {boolean}
*
* @alias FancytreeNode#reRegister
* @requires jquery.fancytree.clones.js
*/
$.ui.fancytree._FancytreeNodeClass.prototype.reRegister = function (
key,
refKey
) {
key = key == null ? null : "" + key;
refKey = refKey == null ? null : "" + refKey;
// this.debug("reRegister", key, refKey);
var tree = this.tree,
prevKey = this.key,
prevRefKey = this.refKey,
keyMap = tree.keyMap,
refMap = tree.refMap,
refList = refMap[prevRefKey] || null,
// curCloneKeys = refList ? node.getCloneList(true),
modified = false;
// Key has changed: update all references
if (key != null && key !== this.key) {
if (keyMap[key]) {
$.error(
"[ext-clones] reRegister(" +
key +
"): already exists: " +
this
);
}
// Update keyMap
delete keyMap[prevKey];
keyMap[key] = this;
// Update refMap
if (refList) {
refMap[prevRefKey] = $.map(refList, function (e) {
return e === prevKey ? key : e;
});
}
this.key = key;
modified = true;
}
// refKey has changed
if (refKey != null && refKey !== this.refKey) {
// Remove previous refKeys
if (refList) {
if (refList.length === 1) {
delete refMap[prevRefKey];
} else {
refMap[prevRefKey] = $.map(refList, function (e) {
return e === prevKey ? null : e;
});
}
}
// Add refKey
if (refMap[refKey]) {
refMap[refKey].append(key);
} else {
refMap[refKey] = [this.key];
}
this.refKey = refKey;
modified = true;
}
return modified;
};
/**
* [ext-clones] Define a refKey for an existing node.
* @param {string} refKey
* @returns {boolean}
*
* @alias FancytreeNode#setRefKey
* @requires jquery.fancytree.clones.js
* @since 2.16
*/
$.ui.fancytree._FancytreeNodeClass.prototype.setRefKey = function (refKey) {
return this.reRegister(null, refKey);
};
/**
* [ext-clones] Return all nodes with a given refKey (null if not found).
* @param {string} refKey
* @param {FancytreeNode} [rootNode] optionally restrict results to descendants of this node
* @returns {FancytreeNode[] | null}
* @alias Fancytree#getNodesByRef
* @requires jquery.fancytree.clones.js
*/
$.ui.fancytree._FancytreeClass.prototype.getNodesByRef = function (
refKey,
rootNode
) {
var keyMap = this.keyMap,
refList = this.refMap[refKey] || null;
if (refList) {
// Convert key list to node list
if (rootNode) {
refList = $.map(refList, function (val) {
var node = keyMap[val];
return node.isDescendantOf(rootNode) ? node : null;
});
} else {
refList = $.map(refList, function (val) {
return keyMap[val];
});
}
if (refList.length < 1) {
refList = null;
}
}
return refList;
};
/**
* [ext-clones] Replace a refKey with a new one.
* @param {string} oldRefKey
* @param {string} newRefKey
* @alias Fancytree#changeRefKey
* @requires jquery.fancytree.clones.js
*/
$.ui.fancytree._FancytreeClass.prototype.changeRefKey = function (
oldRefKey,
newRefKey
) {
var i,
node,
keyMap = this.keyMap,
refList = this.refMap[oldRefKey] || null;
if (refList) {
for (i = 0; i < refList.length; i++) {
node = keyMap[refList[i]];
node.refKey = newRefKey;
}
delete this.refMap[oldRefKey];
this.refMap[newRefKey] = refList;
}
};
/*******************************************************************************
* Extension code
*/
$.ui.fancytree.registerExtension({
name: "clones",
version: "2.38.3",
// Default options for this extension.
options: {
highlightActiveClones: true, // set 'fancytree-active-clone' on active clones and all peers
highlightClones: false, // set 'fancytree-clone' class on any node that has at least one clone
},
treeCreate: function (ctx) {
this._superApply(arguments);
ctx.tree.refMap = {};
ctx.tree.keyMap = {};
},
treeInit: function (ctx) {
this.$container.addClass("fancytree-ext-clones");
_assert(ctx.options.defaultKey == null);
// Generate unique / reproducible default keys
ctx.options.defaultKey = function (node) {
return calcUniqueKey(node);
};
// The default implementation loads initial data
this._superApply(arguments);
},
treeClear: function (ctx) {
ctx.tree.refMap = {};
ctx.tree.keyMap = {};
return this._superApply(arguments);
},
treeRegisterNode: function (ctx, add, node) {
var refList,
len,
tree = ctx.tree,
keyMap = tree.keyMap,
refMap = tree.refMap,
key = node.key,
refKey = node && node.refKey != null ? "" + node.refKey : null;
// ctx.tree.debug("clones.treeRegisterNode", add, node);
if (node.isStatusNode()) {
return this._super(ctx, add, node);
}
if (add) {
if (keyMap[node.key] != null) {
var other = keyMap[node.key],
msg =
"clones.treeRegisterNode: duplicate key '" +
node.key +
"': /" +
node.getPath(true) +
" => " +
other.getPath(true);
// Sometimes this exception is not visible in the console,
// so we also write it:
tree.error(msg);
$.error(msg);
}
keyMap[key] = node;
if (refKey) {
refList = refMap[refKey];
if (refList) {
refList.push(key);
if (
refList.length === 2 &&
ctx.options.clones.highlightClones
) {
// Mark peer node, if it just became a clone (no need to
// mark current node, since it will be rendered later anyway)
keyMap[refList[0]].renderStatus();
}
} else {
refMap[refKey] = [key];
}
// node.debug("clones.treeRegisterNode: add clone =>", refMap[refKey]);
}
} else {
if (keyMap[key] == null) {
$.error(
"clones.treeRegisterNode: node.key not registered: " +
node.key
);
}
delete keyMap[key];
if (refKey) {
refList = refMap[refKey];
// node.debug("clones.treeRegisterNode: remove clone BEFORE =>", refMap[refKey]);
if (refList) {
len = refList.length;
if (len <= 1) {
_assert(len === 1);
_assert(refList[0] === key);
delete refMap[refKey];
} else {
_removeArrayMember(refList, key);
// Unmark peer node, if this was the only clone
if (
len === 2 &&
ctx.options.clones.highlightClones
) {
// node.debug("clones.treeRegisterNode: last =>", node.getCloneList());
keyMap[refList[0]].renderStatus();
}
}
// node.debug("clones.treeRegisterNode: remove clone =>", refMap[refKey]);
}
}
}
return this._super(ctx, add, node);
},
nodeRenderStatus: function (ctx) {
var $span,
res,
node = ctx.node;
res = this._super(ctx);
if (ctx.options.clones.highlightClones) {
$span = $(node[ctx.tree.statusClassPropName]);
// Only if span already exists
if ($span.length && node.isClone()) {
// node.debug("clones.nodeRenderStatus: ", ctx.options.clones.highlightClones);
$span.addClass("fancytree-clone");
}
}
return res;
},
nodeSetActive: function (ctx, flag, callOpts) {
var res,
scpn = ctx.tree.statusClassPropName,
node = ctx.node;
res = this._superApply(arguments);
if (ctx.options.clones.highlightActiveClones && node.isClone()) {
$.each(node.getCloneList(true), function (idx, n) {
// n.debug("clones.nodeSetActive: ", flag !== false);
$(n[scpn]).toggleClass(
"fancytree-active-clone",
flag !== false
);
});
}
return res;
},
});
// Value returned by `require('jquery.fancytree..')`
return $.ui.fancytree;
}); // End of closure

View file

@ -0,0 +1,205 @@
/*!
* jquery.fancytree.columnview.js
*
* Render tree like a Mac Finder's column view.
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
*
* Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
*
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.38.3
* @date 2023-02-01T20:52:50Z
*/
(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";
/*******************************************************************************
* Private functions and variables
*/
var _assert = $.ui.fancytree.assert,
FT = $.ui.fancytree;
/*******************************************************************************
* Private functions and variables
*/
$.ui.fancytree.registerExtension({
name: "columnview",
version: "2.38.3",
// Default options for this extension.
options: {},
// Overide virtual methods for this extension.
// `this` : is this extension object
// `this._base` : the Fancytree instance
// `this._super`: the virtual function that was overriden (member of prev. extension or Fancytree)
treeInit: function (ctx) {
var $tdFirst,
$ul,
tree = ctx.tree,
$table = tree.widget.element;
tree.tr = $("tbody tr", $table)[0];
tree.$tdList = $(">td", tree.tr);
tree.columnCount = tree.$tdList.length;
// Perform default behavior
this._superApply(arguments);
// Standard Fancytree created a root <ul>. Now move this into first table cell
$ul = $(tree.rootNode.ul);
$tdFirst = tree.$tdList.eq(0);
_assert(
$.inArray("table", this.options.extensions) < 0,
"columnview extensions must not use ext-table"
);
_assert(
tree.columnCount >= 2,
"columnview target must be a table with at least two columns"
);
$ul.removeClass("fancytree-container").removeAttr("tabindex");
tree.$container = $table;
$table
.addClass("fancytree-container fancytree-ext-columnview")
.attr("tabindex", "0");
$tdFirst.empty();
$ul.detach().appendTo($tdFirst);
// Force some required options
tree.widget.options.autoCollapse = true;
// tree.widget.options.autoActivate = true;
tree.widget.options.toggleEffect = false;
tree.widget.options.clickFolderMode = 1;
$table
// Make sure that only active path is expanded when a node is activated:
.on("fancytreeactivate", function (event, data) {
var node = data.node,
tree = data.tree,
level = node.getLevel();
tree._callHook("nodeCollapseSiblings", node);
// Clear right neighbours
if (!node.expanded) {
tree.$tdList.eq(level).nextAll().empty();
}
// Expand nodes on activate, so we populate the right neighbor cell
if (!node.expanded && (node.children || node.lazy)) {
node.setExpanded();
}
})
// Adjust keyboard behaviour:
.on("fancytreekeydown", function (event, data) {
var next = null,
handled = true,
node = data.node || data.tree.getFirstChild();
if (node.getLevel() >= tree.columnCount) {
return;
}
switch (FT.eventToString(event)) {
case "down":
next = node.getNextSibling();
break;
case "left":
if (!node.isTopLevel()) {
next = node.getParent();
}
break;
case "right":
next = node.getFirstChild();
if (!next) {
// default processing: expand or ignore
return;
}
// Prefer an expanded child if any
next.visitSiblings(function (n) {
if (n.expanded) {
next = n;
return false;
}
}, true);
break;
case "up":
next = node.getPrevSibling();
break;
default:
handled = false;
}
if (next) {
next.setActive();
}
return !handled;
});
},
nodeSetExpanded: function (ctx, flag, callOpts) {
var $wait,
node = ctx.node,
tree = ctx.tree,
level = node.getLevel();
if (flag !== false && !node.expanded && node.isUndefined()) {
$wait = $(
"<span class='fancytree-icon fancytree-icon-loading'>"
);
tree.$tdList.eq(level).empty().append($wait);
}
return this._superApply(arguments);
},
nodeRemoveChildren: function (ctx) {
// #899: node's children removed: remove child marker...
$(ctx.node.span).find("span.fancytree-cv-right").remove();
// ...and clear right columns
ctx.tree.$tdList.eq(ctx.node.getLevel()).nextAll().empty();
return this._superApply(arguments);
},
nodeRender: function (ctx, force, deep, collapsed, _recursive) {
// Render standard nested <ul> - <li> hierarchy
this._super(ctx, force, deep, collapsed, _recursive);
// Remove expander and add a trailing triangle instead
var level,
$tdChild,
$ul,
tree = ctx.tree,
node = ctx.node,
$span = $(node.span);
$span.find("span.fancytree-expander").remove();
if (
node.hasChildren() !== false &&
!$span.find("span.fancytree-cv-right").length
) {
$span.append(
$("<span class='fancytree-icon fancytree-cv-right'>")
);
}
// Move <ul> with children into the appropriate <td>
if (node.ul && node.expanded) {
node.ul.style.display = ""; // might be hidden if RIGHT was pressed
level = node.getLevel();
if (level < tree.columnCount) {
// only if we are not in the last column
$tdChild = tree.$tdList.eq(level);
$ul = $(node.ul).detach();
$tdChild.empty().append($ul);
}
}
},
});
// Value returned by `require('jquery.fancytree..')`
return $.ui.fancytree;
}); // End of closure

View file

@ -0,0 +1,798 @@
/*!
* jquery.fancytree.dnd.js
*
* Drag-and-drop support (jQuery UI draggable/droppable).
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
*
* Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
*
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.38.3
* @date 2023-02-01T20:52:50Z
*/
(function (factory) {
if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
define([
"jquery",
"jquery-ui/ui/widgets/draggable",
"jquery-ui/ui/widgets/droppable",
"./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";
/******************************************************************************
* Private functions and variables
*/
var didRegisterDnd = false,
classDropAccept = "fancytree-drop-accept",
classDropAfter = "fancytree-drop-after",
classDropBefore = "fancytree-drop-before",
classDropOver = "fancytree-drop-over",
classDropReject = "fancytree-drop-reject",
classDropTarget = "fancytree-drop-target";
/* Convert number to string and prepend +/-; return empty string for 0.*/
function offsetString(n) {
// eslint-disable-next-line no-nested-ternary
return n === 0 ? "" : n > 0 ? "+" + n : "" + n;
}
//--- Extend ui.draggable event handling --------------------------------------
function _registerDnd() {
if (didRegisterDnd) {
return;
}
// Register proxy-functions for draggable.start/drag/stop
$.ui.plugin.add("draggable", "connectToFancytree", {
start: function (event, ui) {
// 'draggable' was renamed to 'ui-draggable' since jQueryUI 1.10
var draggable =
$(this).data("ui-draggable") ||
$(this).data("draggable"),
sourceNode = ui.helper.data("ftSourceNode") || null;
if (sourceNode) {
// Adjust helper offset, so cursor is slightly outside top/left corner
draggable.offset.click.top = -2;
draggable.offset.click.left = +16;
// Trigger dragStart event
// TODO: when called as connectTo..., the return value is ignored(?)
return sourceNode.tree.ext.dnd._onDragEvent(
"start",
sourceNode,
null,
event,
ui,
draggable
);
}
},
drag: function (event, ui) {
var ctx,
isHelper,
logObject,
// 'draggable' was renamed to 'ui-draggable' since jQueryUI 1.10
draggable =
$(this).data("ui-draggable") ||
$(this).data("draggable"),
sourceNode = ui.helper.data("ftSourceNode") || null,
prevTargetNode = ui.helper.data("ftTargetNode") || null,
targetNode = $.ui.fancytree.getNode(event.target),
dndOpts = sourceNode && sourceNode.tree.options.dnd;
// logObject = sourceNode || prevTargetNode || $.ui.fancytree;
// logObject.debug("Drag event:", event, event.shiftKey);
if (event.target && !targetNode) {
// We got a drag event, but the targetNode could not be found
// at the event location. This may happen,
// 1. if the mouse jumped over the drag helper,
// 2. or if a non-fancytree element is dragged
// We ignore it:
isHelper =
$(event.target).closest(
"div.fancytree-drag-helper,#fancytree-drop-marker"
).length > 0;
if (isHelper) {
logObject =
sourceNode || prevTargetNode || $.ui.fancytree;
logObject.debug("Drag event over helper: ignored.");
return;
}
}
ui.helper.data("ftTargetNode", targetNode);
if (dndOpts && dndOpts.updateHelper) {
ctx = sourceNode.tree._makeHookContext(sourceNode, event, {
otherNode: targetNode,
ui: ui,
draggable: draggable,
dropMarker: $("#fancytree-drop-marker"),
});
dndOpts.updateHelper.call(sourceNode.tree, sourceNode, ctx);
}
// Leaving a tree node
if (prevTargetNode && prevTargetNode !== targetNode) {
prevTargetNode.tree.ext.dnd._onDragEvent(
"leave",
prevTargetNode,
sourceNode,
event,
ui,
draggable
);
}
if (targetNode) {
if (!targetNode.tree.options.dnd.dragDrop) {
// not enabled as drop target
} else if (targetNode === prevTargetNode) {
// Moving over same node
targetNode.tree.ext.dnd._onDragEvent(
"over",
targetNode,
sourceNode,
event,
ui,
draggable
);
} else {
// Entering this node first time
targetNode.tree.ext.dnd._onDragEvent(
"enter",
targetNode,
sourceNode,
event,
ui,
draggable
);
targetNode.tree.ext.dnd._onDragEvent(
"over",
targetNode,
sourceNode,
event,
ui,
draggable
);
}
}
// else go ahead with standard event handling
},
stop: function (event, ui) {
var logObject,
// 'draggable' was renamed to 'ui-draggable' since jQueryUI 1.10:
draggable =
$(this).data("ui-draggable") ||
$(this).data("draggable"),
sourceNode = ui.helper.data("ftSourceNode") || null,
targetNode = ui.helper.data("ftTargetNode") || null,
dropped = event.type === "mouseup" && event.which === 1;
if (!dropped) {
logObject = sourceNode || targetNode || $.ui.fancytree;
logObject.debug("Drag was cancelled");
}
if (targetNode) {
if (dropped) {
targetNode.tree.ext.dnd._onDragEvent(
"drop",
targetNode,
sourceNode,
event,
ui,
draggable
);
}
targetNode.tree.ext.dnd._onDragEvent(
"leave",
targetNode,
sourceNode,
event,
ui,
draggable
);
}
if (sourceNode) {
sourceNode.tree.ext.dnd._onDragEvent(
"stop",
sourceNode,
null,
event,
ui,
draggable
);
}
},
});
didRegisterDnd = true;
}
/******************************************************************************
* Drag and drop support
*/
function _initDragAndDrop(tree) {
var dnd = tree.options.dnd || null,
glyph = tree.options.glyph || null;
// Register 'connectToFancytree' option with ui.draggable
if (dnd) {
_registerDnd();
}
// Attach ui.draggable to this Fancytree instance
if (dnd && dnd.dragStart) {
tree.widget.element.draggable(
$.extend(
{
addClasses: false,
// DT issue 244: helper should be child of scrollParent:
appendTo: tree.$container,
// appendTo: "body",
containment: false,
// containment: "parent",
delay: 0,
distance: 4,
revert: false,
scroll: true, // to disable, also set css 'position: inherit' on ul.fancytree-container
scrollSpeed: 7,
scrollSensitivity: 10,
// Delegate draggable.start, drag, and stop events to our handler
connectToFancytree: true,
// Let source tree create the helper element
helper: function (event) {
var $helper,
$nodeTag,
opts,
sourceNode = $.ui.fancytree.getNode(
event.target
);
if (!sourceNode) {
// #405, DT issue 211: might happen, if dragging a table *header*
return "<div>ERROR?: helper requested but sourceNode not found</div>";
}
opts = sourceNode.tree.options.dnd;
$nodeTag = $(sourceNode.span);
// Only event and node argument is available
$helper = $(
"<div class='fancytree-drag-helper'><span class='fancytree-drag-helper-img' /></div>"
)
.css({ zIndex: 3, position: "relative" }) // so it appears above ext-wide selection bar
.append(
$nodeTag
.find("span.fancytree-title")
.clone()
);
// Attach node reference to helper object
$helper.data("ftSourceNode", sourceNode);
// Support glyph symbols instead of icons
if (glyph) {
$helper
.find(".fancytree-drag-helper-img")
.addClass(
glyph.map._addClass +
" " +
glyph.map.dragHelper
);
}
// Allow to modify the helper, e.g. to add multi-node-drag feedback
if (opts.initHelper) {
opts.initHelper.call(
sourceNode.tree,
sourceNode,
{
node: sourceNode,
tree: sourceNode.tree,
originalEvent: event,
ui: { helper: $helper },
}
);
}
// We return an unconnected element, so `draggable` will add this
// to the parent specified as `appendTo` option
return $helper;
},
start: function (event, ui) {
var sourceNode = ui.helper.data("ftSourceNode");
return !!sourceNode; // Abort dragging if no node could be found
},
},
tree.options.dnd.draggable
)
);
}
// Attach ui.droppable to this Fancytree instance
if (dnd && dnd.dragDrop) {
tree.widget.element.droppable(
$.extend(
{
addClasses: false,
tolerance: "intersect",
greedy: false,
/*
activate: function(event, ui) {
tree.debug("droppable - activate", event, ui, this);
},
create: function(event, ui) {
tree.debug("droppable - create", event, ui);
},
deactivate: function(event, ui) {
tree.debug("droppable - deactivate", event, ui);
},
drop: function(event, ui) {
tree.debug("droppable - drop", event, ui);
},
out: function(event, ui) {
tree.debug("droppable - out", event, ui);
},
over: function(event, ui) {
tree.debug("droppable - over", event, ui);
}
*/
},
tree.options.dnd.droppable
)
);
}
}
/******************************************************************************
*
*/
$.ui.fancytree.registerExtension({
name: "dnd",
version: "2.38.3",
// Default options for this extension.
options: {
// Make tree nodes accept draggables
autoExpandMS: 1000, // Expand nodes after n milliseconds of hovering.
draggable: null, // Additional options passed to jQuery draggable
droppable: null, // Additional options passed to jQuery droppable
focusOnClick: false, // Focus, although draggable cancels mousedown event (#270)
preventVoidMoves: true, // Prevent dropping nodes 'before self', etc.
preventRecursiveMoves: true, // Prevent dropping nodes on own descendants
smartRevert: true, // set draggable.revert = true if drop was rejected
dropMarkerOffsetX: -24, // absolute position offset for .fancytree-drop-marker relatively to ..fancytree-title (icon/img near a node accepting drop)
dropMarkerInsertOffsetX: -16, // additional offset for drop-marker with hitMode = "before"/"after"
// Events (drag support)
dragStart: null, // Callback(sourceNode, data), return true, to enable dnd
dragStop: null, // Callback(sourceNode, data)
initHelper: null, // Callback(sourceNode, data)
updateHelper: null, // Callback(sourceNode, data)
// Events (drop support)
dragEnter: null, // Callback(targetNode, data)
dragOver: null, // Callback(targetNode, data)
dragExpand: null, // Callback(targetNode, data), return false to prevent autoExpand
dragDrop: null, // Callback(targetNode, data)
dragLeave: null, // Callback(targetNode, data)
},
treeInit: function (ctx) {
var tree = ctx.tree;
this._superApply(arguments);
// issue #270: draggable eats mousedown events
if (tree.options.dnd.dragStart) {
tree.$container.on("mousedown", function (event) {
// if( !tree.hasFocus() && ctx.options.dnd.focusOnClick ) {
if (ctx.options.dnd.focusOnClick) {
// #270
var node = $.ui.fancytree.getNode(event);
if (node) {
node.debug(
"Re-enable focus that was prevented by jQuery UI draggable."
);
// node.setFocus();
// $(node.span).closest(":tabbable").focus();
// $(event.target).trigger("focus");
// $(event.target).closest(":tabbable").trigger("focus");
}
setTimeout(function () {
// #300
$(event.target).closest(":tabbable").focus();
}, 10);
}
});
}
_initDragAndDrop(tree);
},
/* Display drop marker according to hitMode ('after', 'before', 'over'). */
_setDndStatus: function (
sourceNode,
targetNode,
helper,
hitMode,
accept
) {
var markerOffsetX,
pos,
markerAt = "center",
instData = this._local,
dndOpt = this.options.dnd,
glyphOpt = this.options.glyph,
$source = sourceNode ? $(sourceNode.span) : null,
$target = $(targetNode.span),
$targetTitle = $target.find("span.fancytree-title");
if (!instData.$dropMarker) {
instData.$dropMarker = $(
"<div id='fancytree-drop-marker'></div>"
)
.hide()
.css({ "z-index": 1000 })
.prependTo($(this.$div).parent());
// .prependTo("body");
if (glyphOpt) {
instData.$dropMarker.addClass(
glyphOpt.map._addClass + " " + glyphOpt.map.dropMarker
);
}
}
if (
hitMode === "after" ||
hitMode === "before" ||
hitMode === "over"
) {
markerOffsetX = dndOpt.dropMarkerOffsetX || 0;
switch (hitMode) {
case "before":
markerAt = "top";
markerOffsetX += dndOpt.dropMarkerInsertOffsetX || 0;
break;
case "after":
markerAt = "bottom";
markerOffsetX += dndOpt.dropMarkerInsertOffsetX || 0;
break;
}
pos = {
my: "left" + offsetString(markerOffsetX) + " center",
at: "left " + markerAt,
of: $targetTitle,
};
if (this.options.rtl) {
pos.my = "right" + offsetString(-markerOffsetX) + " center";
pos.at = "right " + markerAt;
}
instData.$dropMarker
.toggleClass(classDropAfter, hitMode === "after")
.toggleClass(classDropOver, hitMode === "over")
.toggleClass(classDropBefore, hitMode === "before")
.toggleClass("fancytree-rtl", !!this.options.rtl)
.show()
.position($.ui.fancytree.fixPositionOptions(pos));
} else {
instData.$dropMarker.hide();
}
if ($source) {
$source
.toggleClass(classDropAccept, accept === true)
.toggleClass(classDropReject, accept === false);
}
$target
.toggleClass(
classDropTarget,
hitMode === "after" ||
hitMode === "before" ||
hitMode === "over"
)
.toggleClass(classDropAfter, hitMode === "after")
.toggleClass(classDropBefore, hitMode === "before")
.toggleClass(classDropAccept, accept === true)
.toggleClass(classDropReject, accept === false);
helper
.toggleClass(classDropAccept, accept === true)
.toggleClass(classDropReject, accept === false);
},
/*
* Handles drag'n'drop functionality.
*
* A standard jQuery drag-and-drop process may generate these calls:
*
* start:
* _onDragEvent("start", sourceNode, null, event, ui, draggable);
* drag:
* _onDragEvent("leave", prevTargetNode, sourceNode, event, ui, draggable);
* _onDragEvent("over", targetNode, sourceNode, event, ui, draggable);
* _onDragEvent("enter", targetNode, sourceNode, event, ui, draggable);
* stop:
* _onDragEvent("drop", targetNode, sourceNode, event, ui, draggable);
* _onDragEvent("leave", targetNode, sourceNode, event, ui, draggable);
* _onDragEvent("stop", sourceNode, null, event, ui, draggable);
*/
_onDragEvent: function (
eventName,
node,
otherNode,
event,
ui,
draggable
) {
// if(eventName !== "over"){
// this.debug("tree.ext.dnd._onDragEvent(%s, %o, %o) - %o", eventName, node, otherNode, this);
// }
var accept,
nodeOfs,
parentRect,
rect,
relPos,
relPos2,
enterResponse,
hitMode,
r,
opts = this.options,
dnd = opts.dnd,
ctx = this._makeHookContext(node, event, {
otherNode: otherNode,
ui: ui,
draggable: draggable,
}),
res = null,
self = this,
$nodeTag = $(node.span);
if (dnd.smartRevert) {
draggable.options.revert = "invalid";
}
switch (eventName) {
case "start":
if (node.isStatusNode()) {
res = false;
} else if (dnd.dragStart) {
res = dnd.dragStart(node, ctx);
}
if (res === false) {
this.debug("tree.dragStart() cancelled");
//draggable._clear();
// NOTE: the return value seems to be ignored (drag is not cancelled, when false is returned)
// TODO: call this._cancelDrag()?
ui.helper.trigger("mouseup").hide();
} else {
if (dnd.smartRevert) {
// #567, #593: fix revert position
// rect = node.li.getBoundingClientRect();
rect =
node[
ctx.tree.nodeContainerAttrName
].getBoundingClientRect();
parentRect = $(
draggable.options.appendTo
)[0].getBoundingClientRect();
draggable.originalPosition.left = Math.max(
0,
rect.left - parentRect.left
);
draggable.originalPosition.top = Math.max(
0,
rect.top - parentRect.top
);
}
$nodeTag.addClass("fancytree-drag-source");
// Register global handlers to allow cancel
$(document).on(
"keydown.fancytree-dnd,mousedown.fancytree-dnd",
function (event) {
// node.tree.debug("dnd global event", event.type, event.which);
if (
event.type === "keydown" &&
event.which === $.ui.keyCode.ESCAPE
) {
self.ext.dnd._cancelDrag();
} else if (event.type === "mousedown") {
self.ext.dnd._cancelDrag();
}
}
);
}
break;
case "enter":
if (
dnd.preventRecursiveMoves &&
node.isDescendantOf(otherNode)
) {
r = false;
} else {
r = dnd.dragEnter ? dnd.dragEnter(node, ctx) : null;
}
if (!r) {
// convert null, undefined, false to false
res = false;
} else if (Array.isArray(r)) {
// TODO: also accept passing an object of this format directly
res = {
over: $.inArray("over", r) >= 0,
before: $.inArray("before", r) >= 0,
after: $.inArray("after", r) >= 0,
};
} else {
res = {
over: r === true || r === "over",
before: r === true || r === "before",
after: r === true || r === "after",
};
}
ui.helper.data("enterResponse", res);
// this.debug("helper.enterResponse: %o", res);
break;
case "over":
enterResponse = ui.helper.data("enterResponse");
hitMode = null;
if (enterResponse === false) {
// Don't call dragOver if onEnter returned false.
// break;
} else if (typeof enterResponse === "string") {
// Use hitMode from onEnter if provided.
hitMode = enterResponse;
} else {
// Calculate hitMode from relative cursor position.
nodeOfs = $nodeTag.offset();
relPos = {
x: event.pageX - nodeOfs.left,
y: event.pageY - nodeOfs.top,
};
relPos2 = {
x: relPos.x / $nodeTag.width(),
y: relPos.y / $nodeTag.height(),
};
if (enterResponse.after && relPos2.y > 0.75) {
hitMode = "after";
} else if (
!enterResponse.over &&
enterResponse.after &&
relPos2.y > 0.5
) {
hitMode = "after";
} else if (enterResponse.before && relPos2.y <= 0.25) {
hitMode = "before";
} else if (
!enterResponse.over &&
enterResponse.before &&
relPos2.y <= 0.5
) {
hitMode = "before";
} else if (enterResponse.over) {
hitMode = "over";
}
// Prevent no-ops like 'before source node'
// TODO: these are no-ops when moving nodes, but not in copy mode
if (dnd.preventVoidMoves) {
if (node === otherNode) {
this.debug(
" drop over source node prevented"
);
hitMode = null;
} else if (
hitMode === "before" &&
otherNode &&
node === otherNode.getNextSibling()
) {
this.debug(
" drop after source node prevented"
);
hitMode = null;
} else if (
hitMode === "after" &&
otherNode &&
node === otherNode.getPrevSibling()
) {
this.debug(
" drop before source node prevented"
);
hitMode = null;
} else if (
hitMode === "over" &&
otherNode &&
otherNode.parent === node &&
otherNode.isLastSibling()
) {
this.debug(
" drop last child over own parent prevented"
);
hitMode = null;
}
}
// this.debug("hitMode: %s - %s - %s", hitMode, (node.parent === otherNode), node.isLastSibling());
ui.helper.data("hitMode", hitMode);
}
// Auto-expand node (only when 'over' the node, not 'before', or 'after')
if (
hitMode !== "before" &&
hitMode !== "after" &&
dnd.autoExpandMS &&
node.hasChildren() !== false &&
!node.expanded &&
(!dnd.dragExpand || dnd.dragExpand(node, ctx) !== false)
) {
node.scheduleAction("expand", dnd.autoExpandMS);
}
if (hitMode && dnd.dragOver) {
// TODO: http://code.google.com/p/dynatree/source/detail?r=625
ctx.hitMode = hitMode;
res = dnd.dragOver(node, ctx);
}
accept = res !== false && hitMode !== null;
if (dnd.smartRevert) {
draggable.options.revert = !accept;
}
this._local._setDndStatus(
otherNode,
node,
ui.helper,
hitMode,
accept
);
break;
case "drop":
hitMode = ui.helper.data("hitMode");
if (hitMode && dnd.dragDrop) {
ctx.hitMode = hitMode;
dnd.dragDrop(node, ctx);
}
break;
case "leave":
// Cancel pending expand request
node.scheduleAction("cancel");
ui.helper.data("enterResponse", null);
ui.helper.data("hitMode", null);
this._local._setDndStatus(
otherNode,
node,
ui.helper,
"out",
undefined
);
if (dnd.dragLeave) {
dnd.dragLeave(node, ctx);
}
break;
case "stop":
$nodeTag.removeClass("fancytree-drag-source");
$(document).off(".fancytree-dnd");
if (dnd.dragStop) {
dnd.dragStop(node, ctx);
}
break;
default:
$.error("Unsupported drag event: " + eventName);
}
return res;
},
_cancelDrag: function () {
var dd = $.ui.ddmanager.current;
if (dd) {
dd.cancel();
}
},
});
// Value returned by `require('jquery.fancytree..')`
return $.ui.fancytree;
}); // End of closure

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,403 @@
/*!
* jquery.fancytree.edit.js
*
* Make node titles editable.
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
*
* Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
*
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.38.3
* @date 2023-02-01T20:52:50Z
*/
(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";
/*******************************************************************************
* Private functions and variables
*/
var isMac = /Mac/.test(navigator.platform),
escapeHtml = $.ui.fancytree.escapeHtml,
trim = $.ui.fancytree.trim,
unescapeHtml = $.ui.fancytree.unescapeHtml;
/**
* [ext-edit] Start inline editing of current node title.
*
* @alias FancytreeNode#editStart
* @requires Fancytree
*/
$.ui.fancytree._FancytreeNodeClass.prototype.editStart = function () {
var $input,
node = this,
tree = this.tree,
local = tree.ext.edit,
instOpts = tree.options.edit,
$title = $(".fancytree-title", node.span),
eventData = {
node: node,
tree: tree,
options: tree.options,
isNew: $(node[tree.statusClassPropName]).hasClass(
"fancytree-edit-new"
),
orgTitle: node.title,
input: null,
dirty: false,
};
// beforeEdit may want to modify the title before editing
if (
instOpts.beforeEdit.call(
node,
{ type: "beforeEdit" },
eventData
) === false
) {
return false;
}
$.ui.fancytree.assert(!local.currentNode, "recursive edit");
local.currentNode = this;
local.eventData = eventData;
// Disable standard Fancytree mouse- and key handling
tree.widget._unbind();
local.lastDraggableAttrValue = node.span.draggable;
if (local.lastDraggableAttrValue) {
node.span.draggable = false;
}
// #116: ext-dnd prevents the blur event, so we have to catch outer clicks
$(document).on("mousedown.fancytree-edit", function (event) {
if (!$(event.target).hasClass("fancytree-edit-input")) {
node.editEnd(true, event);
}
});
// Replace node with <input>
$input = $("<input />", {
class: "fancytree-edit-input",
type: "text",
value: tree.options.escapeTitles
? eventData.orgTitle
: unescapeHtml(eventData.orgTitle),
});
local.eventData.input = $input;
if (instOpts.adjustWidthOfs != null) {
$input.width($title.width() + instOpts.adjustWidthOfs);
}
if (instOpts.inputCss != null) {
$input.css(instOpts.inputCss);
}
$title.html($input);
// Focus <input> and bind keyboard handler
$input
.focus()
.change(function (event) {
$input.addClass("fancytree-edit-dirty");
})
.on("keydown", function (event) {
switch (event.which) {
case $.ui.keyCode.ESCAPE:
node.editEnd(false, event);
break;
case $.ui.keyCode.ENTER:
node.editEnd(true, event);
return false; // so we don't start editmode on Mac
}
event.stopPropagation();
})
.blur(function (event) {
return node.editEnd(true, event);
});
instOpts.edit.call(node, { type: "edit" }, eventData);
};
/**
* [ext-edit] Stop inline editing.
* @param {Boolean} [applyChanges=false] false: cancel edit, true: save (if modified)
* @alias FancytreeNode#editEnd
* @requires jquery.fancytree.edit.js
*/
$.ui.fancytree._FancytreeNodeClass.prototype.editEnd = function (
applyChanges,
_event
) {
var newVal,
node = this,
tree = this.tree,
local = tree.ext.edit,
eventData = local.eventData,
instOpts = tree.options.edit,
$title = $(".fancytree-title", node.span),
$input = $title.find("input.fancytree-edit-input");
if (instOpts.trim) {
$input.val(trim($input.val()));
}
newVal = $input.val();
eventData.dirty = newVal !== node.title;
eventData.originalEvent = _event;
// Find out, if saving is required
if (applyChanges === false) {
// If true/false was passed, honor this (except in rename mode, if unchanged)
eventData.save = false;
} else if (eventData.isNew) {
// In create mode, we save everything, except for empty text
eventData.save = newVal !== "";
} else {
// In rename mode, we save everyting, except for empty or unchanged text
eventData.save = eventData.dirty && newVal !== "";
}
// Allow to break (keep editor open), modify input, or re-define data.save
if (
instOpts.beforeClose.call(
node,
{ type: "beforeClose" },
eventData
) === false
) {
return false;
}
if (
eventData.save &&
instOpts.save.call(node, { type: "save" }, eventData) === false
) {
return false;
}
$input.removeClass("fancytree-edit-dirty").off();
// Unbind outer-click handler
$(document).off(".fancytree-edit");
if (eventData.save) {
// # 171: escape user input (not required if global escaping is on)
node.setTitle(
tree.options.escapeTitles ? newVal : escapeHtml(newVal)
);
node.setFocus();
} else {
if (eventData.isNew) {
node.remove();
node = eventData.node = null;
local.relatedNode.setFocus();
} else {
node.renderTitle();
node.setFocus();
}
}
local.eventData = null;
local.currentNode = null;
local.relatedNode = null;
// Re-enable mouse and keyboard handling
tree.widget._bind();
if (node && local.lastDraggableAttrValue) {
node.span.draggable = true;
}
// Set keyboard focus, even if setFocus() claims 'nothing to do'
tree.$container.get(0).focus({ preventScroll: true });
eventData.input = null;
instOpts.close.call(node, { type: "close" }, eventData);
return true;
};
/**
* [ext-edit] Create a new child or sibling node and start edit mode.
*
* @param {String} [mode='child'] 'before', 'after', or 'child'
* @param {Object} [init] NodeData (or simple title string)
* @alias FancytreeNode#editCreateNode
* @requires jquery.fancytree.edit.js
* @since 2.4
*/
$.ui.fancytree._FancytreeNodeClass.prototype.editCreateNode = function (
mode,
init
) {
var newNode,
tree = this.tree,
self = this;
mode = mode || "child";
if (init == null) {
init = { title: "" };
} else if (typeof init === "string") {
init = { title: init };
} else {
$.ui.fancytree.assert($.isPlainObject(init));
}
// Make sure node is expanded (and loaded) in 'child' mode
if (
mode === "child" &&
!this.isExpanded() &&
this.hasChildren() !== false
) {
this.setExpanded().done(function () {
self.editCreateNode(mode, init);
});
return;
}
newNode = this.addNode(init, mode);
// #644: Don't filter new nodes.
newNode.match = true;
$(newNode[tree.statusClassPropName])
.removeClass("fancytree-hide")
.addClass("fancytree-match");
newNode.makeVisible(/*{noAnimation: true}*/).done(function () {
$(newNode[tree.statusClassPropName]).addClass("fancytree-edit-new");
self.tree.ext.edit.relatedNode = self;
newNode.editStart();
});
};
/**
* [ext-edit] Check if any node in this tree in edit mode.
*
* @returns {FancytreeNode | null}
* @alias Fancytree#isEditing
* @requires jquery.fancytree.edit.js
*/
$.ui.fancytree._FancytreeClass.prototype.isEditing = function () {
return this.ext.edit ? this.ext.edit.currentNode : null;
};
/**
* [ext-edit] Check if this node is in edit mode.
* @returns {Boolean} true if node is currently beeing edited
* @alias FancytreeNode#isEditing
* @requires jquery.fancytree.edit.js
*/
$.ui.fancytree._FancytreeNodeClass.prototype.isEditing = function () {
return this.tree.ext.edit
? this.tree.ext.edit.currentNode === this
: false;
};
/*******************************************************************************
* Extension code
*/
$.ui.fancytree.registerExtension({
name: "edit",
version: "2.38.3",
// Default options for this extension.
options: {
adjustWidthOfs: 4, // null: don't adjust input size to content
allowEmpty: false, // Prevent empty input
inputCss: { minWidth: "3em" },
// triggerCancel: ["esc", "tab", "click"],
triggerStart: ["f2", "mac+enter", "shift+click"],
trim: true, // Trim whitespace before save
// Events:
beforeClose: $.noop, // Return false to prevent cancel/save (data.input is available)
beforeEdit: $.noop, // Return false to prevent edit mode
close: $.noop, // Editor was removed
edit: $.noop, // Editor was opened (available as data.input)
// keypress: $.noop, // Not yet implemented
save: $.noop, // Save data.input.val() or return false to keep editor open
},
// Local attributes
currentNode: null,
treeInit: function (ctx) {
var tree = ctx.tree;
this._superApply(arguments);
this.$container
.addClass("fancytree-ext-edit")
.on("fancytreebeforeupdateviewport", function (event, data) {
var editNode = tree.isEditing();
// When scrolling, the TR may be re-used by another node, so the
// active cell marker an
if (editNode) {
editNode.info("Cancel edit due to scroll event.");
editNode.editEnd(false, event);
}
});
},
nodeClick: function (ctx) {
var eventStr = $.ui.fancytree.eventToString(ctx.originalEvent),
triggerStart = ctx.options.edit.triggerStart;
if (
eventStr === "shift+click" &&
$.inArray("shift+click", triggerStart) >= 0
) {
if (ctx.originalEvent.shiftKey) {
ctx.node.editStart();
return false;
}
}
if (
eventStr === "click" &&
$.inArray("clickActive", triggerStart) >= 0
) {
// Only when click was inside title text (not aynwhere else in the row)
if (
ctx.node.isActive() &&
!ctx.node.isEditing() &&
$(ctx.originalEvent.target).hasClass("fancytree-title")
) {
ctx.node.editStart();
return false;
}
}
return this._superApply(arguments);
},
nodeDblclick: function (ctx) {
if ($.inArray("dblclick", ctx.options.edit.triggerStart) >= 0) {
ctx.node.editStart();
return false;
}
return this._superApply(arguments);
},
nodeKeydown: function (ctx) {
switch (ctx.originalEvent.which) {
case 113: // [F2]
if ($.inArray("f2", ctx.options.edit.triggerStart) >= 0) {
ctx.node.editStart();
return false;
}
break;
case $.ui.keyCode.ENTER:
if (
$.inArray("mac+enter", ctx.options.edit.triggerStart) >=
0 &&
isMac
) {
ctx.node.editStart();
return false;
}
break;
}
return this._superApply(arguments);
},
});
// Value returned by `require('jquery.fancytree..')`
return $.ui.fancytree;
}); // End of closure

View file

@ -0,0 +1,549 @@
/*!
* jquery.fancytree.filter.js
*
* Remove or highlight tree nodes, based on a filter.
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
*
* Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
*
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.38.3
* @date 2023-02-01T20:52:50Z
*/
(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";
/*******************************************************************************
* Private functions and variables
*/
var KeyNoData = "__not_found__",
escapeHtml = $.ui.fancytree.escapeHtml,
exoticStartChar = "\uFFF7",
exoticEndChar = "\uFFF8";
function _escapeRegex(str) {
return (str + "").replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
}
function extractHtmlText(s) {
if (s.indexOf(">") >= 0) {
return $("<div/>").html(s).text();
}
return s;
}
/**
* @description Marks the matching charecters of `text` either by `mark` or
* by exotic*Chars (if `escapeTitles` is `true`) based on `regexMatchArray`
* which is an array of matching groups.
* @param {string} text
* @param {RegExpMatchArray} regexMatchArray
*/
function _markFuzzyMatchedChars(text, regexMatchArray, escapeTitles) {
// It is extremely infuriating that we can not use `let` or `const` or arrow functions.
// Damn you IE!!!
var matchingIndices = [];
// get the indices of matched characters (Iterate through `RegExpMatchArray`)
for (
var _matchingArrIdx = 1;
_matchingArrIdx < regexMatchArray.length;
_matchingArrIdx++
) {
var _mIdx =
// get matching char index by cumulatively adding
// the matched group length
regexMatchArray[_matchingArrIdx].length +
(_matchingArrIdx === 1 ? 0 : 1) +
(matchingIndices[matchingIndices.length - 1] || 0);
matchingIndices.push(_mIdx);
}
// Map each `text` char to its position and store in `textPoses`.
var textPoses = text.split("");
if (escapeTitles) {
// If escaping the title, then wrap the matchng char within exotic chars
matchingIndices.forEach(function (v) {
textPoses[v] = exoticStartChar + textPoses[v] + exoticEndChar;
});
} else {
// Otherwise, Wrap the matching chars within `mark`.
matchingIndices.forEach(function (v) {
textPoses[v] = "<mark>" + textPoses[v] + "</mark>";
});
}
// Join back the modified `textPoses` to create final highlight markup.
return textPoses.join("");
}
$.ui.fancytree._FancytreeClass.prototype._applyFilterImpl = function (
filter,
branchMode,
_opts
) {
var match,
statusNode,
re,
reHighlight,
reExoticStartChar,
reExoticEndChar,
temp,
prevEnableUpdate,
count = 0,
treeOpts = this.options,
escapeTitles = treeOpts.escapeTitles,
prevAutoCollapse = treeOpts.autoCollapse,
opts = $.extend({}, treeOpts.filter, _opts),
hideMode = opts.mode === "hide",
leavesOnly = !!opts.leavesOnly && !branchMode;
// Default to 'match title substring (not case sensitive)'
if (typeof filter === "string") {
if (filter === "") {
this.warn(
"Fancytree passing an empty string as a filter is handled as clearFilter()."
);
this.clearFilter();
return;
}
if (opts.fuzzy) {
// See https://codereview.stackexchange.com/questions/23899/faster-javascript-fuzzy-string-matching-function/23905#23905
// and http://www.quora.com/How-is-the-fuzzy-search-algorithm-in-Sublime-Text-designed
// and http://www.dustindiaz.com/autocomplete-fuzzy-matching
match = filter
.split("")
// Escaping the `filter` will not work because,
// it gets further split into individual characters. So,
// escape each character after splitting
.map(_escapeRegex)
.reduce(function (a, b) {
// create capture groups for parts that comes before
// the character
return a + "([^" + b + "]*)" + b;
}, "");
} else {
match = _escapeRegex(filter); // make sure a '.' is treated literally
}
re = new RegExp(match, "i");
reHighlight = new RegExp(_escapeRegex(filter), "gi");
if (escapeTitles) {
reExoticStartChar = new RegExp(
_escapeRegex(exoticStartChar),
"g"
);
reExoticEndChar = new RegExp(_escapeRegex(exoticEndChar), "g");
}
filter = function (node) {
if (!node.title) {
return false;
}
var text = escapeTitles
? node.title
: extractHtmlText(node.title),
// `.match` instead of `.test` to get the capture groups
res = text.match(re);
if (res && opts.highlight) {
if (escapeTitles) {
if (opts.fuzzy) {
temp = _markFuzzyMatchedChars(
text,
res,
escapeTitles
);
} else {
// #740: we must not apply the marks to escaped entity names, e.g. `&quot;`
// Use some exotic characters to mark matches:
temp = text.replace(reHighlight, function (s) {
return exoticStartChar + s + exoticEndChar;
});
}
// now we can escape the title...
node.titleWithHighlight = escapeHtml(temp)
// ... and finally insert the desired `<mark>` tags
.replace(reExoticStartChar, "<mark>")
.replace(reExoticEndChar, "</mark>");
} else {
if (opts.fuzzy) {
node.titleWithHighlight = _markFuzzyMatchedChars(
text,
res
);
} else {
node.titleWithHighlight = text.replace(
reHighlight,
function (s) {
return "<mark>" + s + "</mark>";
}
);
}
}
// node.debug("filter", escapeTitles, text, node.titleWithHighlight);
}
return !!res;
};
}
this.enableFilter = true;
this.lastFilterArgs = arguments;
prevEnableUpdate = this.enableUpdate(false);
this.$div.addClass("fancytree-ext-filter");
if (hideMode) {
this.$div.addClass("fancytree-ext-filter-hide");
} else {
this.$div.addClass("fancytree-ext-filter-dimm");
}
this.$div.toggleClass(
"fancytree-ext-filter-hide-expanders",
!!opts.hideExpanders
);
// Reset current filter
this.rootNode.subMatchCount = 0;
this.visit(function (node) {
delete node.match;
delete node.titleWithHighlight;
node.subMatchCount = 0;
});
statusNode = this.getRootNode()._findDirectChild(KeyNoData);
if (statusNode) {
statusNode.remove();
}
// Adjust node.hide, .match, and .subMatchCount properties
treeOpts.autoCollapse = false; // #528
this.visit(function (node) {
if (leavesOnly && node.children != null) {
return;
}
var res = filter(node),
matchedByBranch = false;
if (res === "skip") {
node.visit(function (c) {
c.match = false;
}, true);
return "skip";
}
if (!res && (branchMode || res === "branch") && node.parent.match) {
res = true;
matchedByBranch = true;
}
if (res) {
count++;
node.match = true;
node.visitParents(function (p) {
if (p !== node) {
p.subMatchCount += 1;
}
// Expand match (unless this is no real match, but only a node in a matched branch)
if (opts.autoExpand && !matchedByBranch && !p.expanded) {
p.setExpanded(true, {
noAnimation: true,
noEvents: true,
scrollIntoView: false,
});
p._filterAutoExpanded = true;
}
}, true);
}
});
treeOpts.autoCollapse = prevAutoCollapse;
if (count === 0 && opts.nodata && hideMode) {
statusNode = opts.nodata;
if (typeof statusNode === "function") {
statusNode = statusNode();
}
if (statusNode === true) {
statusNode = {};
} else if (typeof statusNode === "string") {
statusNode = { title: statusNode };
}
statusNode = $.extend(
{
statusNodeType: "nodata",
key: KeyNoData,
title: this.options.strings.noData,
},
statusNode
);
this.getRootNode().addNode(statusNode).match = true;
}
// Redraw whole tree
this._callHook("treeStructureChanged", this, "applyFilter");
// this.render();
this.enableUpdate(prevEnableUpdate);
return count;
};
/**
* [ext-filter] Dimm or hide nodes.
*
* @param {function | string} filter
* @param {boolean} [opts={autoExpand: false, leavesOnly: false}]
* @returns {integer} count
* @alias Fancytree#filterNodes
* @requires jquery.fancytree.filter.js
*/
$.ui.fancytree._FancytreeClass.prototype.filterNodes = function (
filter,
opts
) {
if (typeof opts === "boolean") {
opts = { leavesOnly: opts };
this.warn(
"Fancytree.filterNodes() leavesOnly option is deprecated since 2.9.0 / 2015-04-19. Use opts.leavesOnly instead."
);
}
return this._applyFilterImpl(filter, false, opts);
};
/**
* [ext-filter] Dimm or hide whole branches.
*
* @param {function | string} filter
* @param {boolean} [opts={autoExpand: false}]
* @returns {integer} count
* @alias Fancytree#filterBranches
* @requires jquery.fancytree.filter.js
*/
$.ui.fancytree._FancytreeClass.prototype.filterBranches = function (
filter,
opts
) {
return this._applyFilterImpl(filter, true, opts);
};
/**
* [ext-filter] Re-apply current filter.
*
* @returns {integer} count
* @alias Fancytree#updateFilter
* @requires jquery.fancytree.filter.js
* @since 2.38
*/
$.ui.fancytree._FancytreeClass.prototype.updateFilter = function () {
if (
this.enableFilter &&
this.lastFilterArgs &&
this.options.filter.autoApply
) {
this._applyFilterImpl.apply(this, this.lastFilterArgs);
} else {
this.warn("updateFilter(): no filter active.");
}
};
/**
* [ext-filter] Reset the filter.
*
* @alias Fancytree#clearFilter
* @requires jquery.fancytree.filter.js
*/
$.ui.fancytree._FancytreeClass.prototype.clearFilter = function () {
var $title,
statusNode = this.getRootNode()._findDirectChild(KeyNoData),
escapeTitles = this.options.escapeTitles,
enhanceTitle = this.options.enhanceTitle,
prevEnableUpdate = this.enableUpdate(false);
if (statusNode) {
statusNode.remove();
}
// we also counted root node's subMatchCount
delete this.rootNode.match;
delete this.rootNode.subMatchCount;
this.visit(function (node) {
if (node.match && node.span) {
// #491, #601
$title = $(node.span).find(">span.fancytree-title");
if (escapeTitles) {
$title.text(node.title);
} else {
$title.html(node.title);
}
if (enhanceTitle) {
enhanceTitle(
{ type: "enhanceTitle" },
{ node: node, $title: $title }
);
}
}
delete node.match;
delete node.subMatchCount;
delete node.titleWithHighlight;
if (node.$subMatchBadge) {
node.$subMatchBadge.remove();
delete node.$subMatchBadge;
}
if (node._filterAutoExpanded && node.expanded) {
node.setExpanded(false, {
noAnimation: true,
noEvents: true,
scrollIntoView: false,
});
}
delete node._filterAutoExpanded;
});
this.enableFilter = false;
this.lastFilterArgs = null;
this.$div.removeClass(
"fancytree-ext-filter fancytree-ext-filter-dimm fancytree-ext-filter-hide"
);
this._callHook("treeStructureChanged", this, "clearFilter");
// this.render();
this.enableUpdate(prevEnableUpdate);
};
/**
* [ext-filter] Return true if a filter is currently applied.
*
* @returns {Boolean}
* @alias Fancytree#isFilterActive
* @requires jquery.fancytree.filter.js
* @since 2.13
*/
$.ui.fancytree._FancytreeClass.prototype.isFilterActive = function () {
return !!this.enableFilter;
};
/**
* [ext-filter] Return true if this node is matched by current filter (or no filter is active).
*
* @returns {Boolean}
* @alias FancytreeNode#isMatched
* @requires jquery.fancytree.filter.js
* @since 2.13
*/
$.ui.fancytree._FancytreeNodeClass.prototype.isMatched = function () {
return !(this.tree.enableFilter && !this.match);
};
/*******************************************************************************
* Extension code
*/
$.ui.fancytree.registerExtension({
name: "filter",
version: "2.38.3",
// Default options for this extension.
options: {
autoApply: true, // Re-apply last filter if lazy data is loaded
autoExpand: false, // Expand all branches that contain matches while filtered
counter: true, // Show a badge with number of matching child nodes near parent icons
fuzzy: false, // Match single characters in order, e.g. 'fb' will match 'FooBar'
hideExpandedCounter: true, // Hide counter badge if parent is expanded
hideExpanders: false, // Hide expanders if all child nodes are hidden by filter
highlight: true, // Highlight matches by wrapping inside <mark> tags
leavesOnly: false, // Match end nodes only
nodata: true, // Display a 'no data' status node if result is empty
mode: "dimm", // Grayout unmatched nodes (pass "hide" to remove unmatched node instead)
},
nodeLoadChildren: function (ctx, source) {
var tree = ctx.tree;
return this._superApply(arguments).done(function () {
if (
tree.enableFilter &&
tree.lastFilterArgs &&
ctx.options.filter.autoApply
) {
tree._applyFilterImpl.apply(tree, tree.lastFilterArgs);
}
});
},
nodeSetExpanded: function (ctx, flag, callOpts) {
var node = ctx.node;
delete node._filterAutoExpanded;
// Make sure counter badge is displayed again, when node is beeing collapsed
if (
!flag &&
ctx.options.filter.hideExpandedCounter &&
node.$subMatchBadge
) {
node.$subMatchBadge.show();
}
return this._superApply(arguments);
},
nodeRenderStatus: function (ctx) {
// Set classes for current status
var res,
node = ctx.node,
tree = ctx.tree,
opts = ctx.options.filter,
$title = $(node.span).find("span.fancytree-title"),
$span = $(node[tree.statusClassPropName]),
enhanceTitle = ctx.options.enhanceTitle,
escapeTitles = ctx.options.escapeTitles;
res = this._super(ctx);
// nothing to do, if node was not yet rendered
if (!$span.length || !tree.enableFilter) {
return res;
}
$span
.toggleClass("fancytree-match", !!node.match)
.toggleClass("fancytree-submatch", !!node.subMatchCount)
.toggleClass(
"fancytree-hide",
!(node.match || node.subMatchCount)
);
// Add/update counter badge
if (
opts.counter &&
node.subMatchCount &&
(!node.isExpanded() || !opts.hideExpandedCounter)
) {
if (!node.$subMatchBadge) {
node.$subMatchBadge = $(
"<span class='fancytree-childcounter'/>"
);
$(
"span.fancytree-icon, span.fancytree-custom-icon",
node.span
).append(node.$subMatchBadge);
}
node.$subMatchBadge.show().text(node.subMatchCount);
} else if (node.$subMatchBadge) {
node.$subMatchBadge.hide();
}
// node.debug("nodeRenderStatus", node.titleWithHighlight, node.title)
// #601: also check for $title.length, because we don't need to render
// if node.span is null (i.e. not rendered)
if (node.span && (!node.isEditing || !node.isEditing.call(node))) {
if (node.titleWithHighlight) {
$title.html(node.titleWithHighlight);
} else if (escapeTitles) {
$title.text(node.title);
} else {
$title.html(node.title);
}
if (enhanceTitle) {
enhanceTitle(
{ type: "enhanceTitle" },
{ node: node, $title: $title }
);
}
}
return res;
},
});
// Value returned by `require('jquery.fancytree..')`
return $.ui.fancytree;
}); // End of closure

View file

@ -0,0 +1,674 @@
/*!
* jquery.fancytree.fixed.js
*
* Add fixed colums and headers to ext.table.
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
*
* Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
*
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.38.3
* @date 2023-02-01T20:52:50Z
*/
// Allow to use multiple var statements inside a function
(function (factory) {
if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
define([
"jquery",
"./jquery.fancytree",
"./jquery.fancytree.table",
], factory);
} else if (typeof module === "object" && module.exports) {
// Node/CommonJS
require("./jquery.fancytree.table"); // core + table
module.exports = factory(require("jquery"));
} else {
// Browser globals
factory(jQuery);
}
})(function ($) {
"use strict";
/******************************************************************************
* Private functions and variables
*/
$.ui.fancytree.registerExtension({
name: "fixed",
version: "0.0.1",
// Default options for this extension.
options: {
fixCol: 1,
fixColWidths: null,
fixRows: true,
scrollSpeed: 50,
resizable: true,
classNames: {
table: "fancytree-ext-fixed",
wrapper: "fancytree-ext-fixed-wrapper",
topLeft: "fancytree-ext-fixed-wrapper-tl",
topRight: "fancytree-ext-fixed-wrapper-tr",
bottomLeft: "fancytree-ext-fixed-wrapper-bl",
bottomRight: "fancytree-ext-fixed-wrapper-br",
hidden: "fancytree-ext-fixed-hidden",
counterpart: "fancytree-ext-fixed-node-counterpart",
scrollBorderBottom: "fancytree-ext-fixed-scroll-border-bottom",
scrollBorderRight: "fancytree-ext-fixed-scroll-border-right",
hover: "fancytree-ext-fixed-hover",
},
},
// Overide virtual methods for this extension.
// `this` : is this extension object
// `this._super`: the virtual function that was overriden (member of prev. extension or Fancytree)
treeInit: function (ctx) {
this._requireExtension("table", true, true);
// 'fixed' requires the table extension to be loaded before itself
var res = this._superApply(arguments),
tree = ctx.tree,
options = this.options.fixed,
fcn = this.options.fixed.classNames,
$table = tree.widget.element,
fixedColCount = options.fixCols,
fixedRowCount = options.fixRows,
$tableWrapper = $table.parent(),
$topLeftWrapper = $("<div>").addClass(fcn.topLeft),
$topRightWrapper = $("<div>").addClass(fcn.topRight),
$bottomLeftWrapper = $("<div>").addClass(fcn.bottomLeft),
$bottomRightWrapper = $("<div>").addClass(fcn.bottomRight),
tableStyle = $table.attr("style"),
tableClass = $table.attr("class"),
$topLeftTable = $("<table>")
.attr("style", tableStyle)
.attr("class", tableClass),
$topRightTable = $("<table>")
.attr("style", tableStyle)
.attr("class", tableClass),
$bottomLeftTable = $table,
$bottomRightTable = $("<table>")
.attr("style", tableStyle)
.attr("class", tableClass),
$head = $table.find("thead"),
$colgroup = $table.find("colgroup"),
headRowCount = $head.find("tr").length;
this.$fixedWrapper = $tableWrapper;
$table.addClass(fcn.table);
$tableWrapper.addClass(fcn.wrapper);
$bottomRightTable.append($("<tbody>"));
if ($colgroup.length) {
$colgroup.remove();
}
if (typeof fixedRowCount === "boolean") {
fixedRowCount = fixedRowCount ? headRowCount : 0;
} else {
fixedRowCount = Math.max(
0,
Math.min(fixedRowCount, headRowCount)
);
}
if (fixedRowCount) {
$topLeftTable.append($head.clone(true));
$topRightTable.append($head.clone(true));
$head.remove();
}
$topLeftTable.find("tr").each(function (idx) {
$(this).find("th").slice(fixedColCount).remove();
});
$topRightTable.find("tr").each(function (idx) {
$(this).find("th").slice(0, fixedColCount).remove();
});
this.$fixedWrapper = $tableWrapper;
$tableWrapper.append(
$topLeftWrapper.append($topLeftTable),
$topRightWrapper.append($topRightTable),
$bottomLeftWrapper.append($bottomLeftTable),
$bottomRightWrapper.append($bottomRightTable)
);
$bottomRightTable.on("keydown", function (evt) {
var node = tree.focusNode,
ctx = tree._makeHookContext(node || tree, evt),
res = tree._callHook("nodeKeydown", ctx);
return res;
});
$bottomRightTable.on("click dblclick", "tr", function (evt) {
var $trLeft = $(this),
$trRight = $trLeft.data(fcn.counterpart),
node = $.ui.fancytree.getNode($trRight),
ctx = tree._makeHookContext(node, evt),
et = $.ui.fancytree.getEventTarget(evt),
prevPhase = tree.phase;
try {
tree.phase = "userEvent";
switch (evt.type) {
case "click":
ctx.targetType = et.type;
if (node.isPagingNode()) {
return (
tree._triggerNodeEvent(
"clickPaging",
ctx,
evt
) === true
);
}
return tree._triggerNodeEvent("click", ctx, evt) ===
false
? false
: tree._callHook("nodeClick", ctx);
case "dblclick":
ctx.targetType = et.type;
return tree._triggerNodeEvent(
"dblclick",
ctx,
evt
) === false
? false
: tree._callHook("nodeDblclick", ctx);
}
} finally {
tree.phase = prevPhase;
}
});
$tableWrapper
.on(
"mouseenter",
"." +
fcn.bottomRight +
" table tr, ." +
fcn.bottomLeft +
" table tr",
function (evt) {
var $tr = $(this),
$trOther = $tr.data(fcn.counterpart);
$tr.addClass(fcn.hover);
$trOther.addClass(fcn.hover);
}
)
.on(
"mouseleave",
"." +
fcn.bottomRight +
" table tr, ." +
fcn.bottomLeft +
" table tr",
function (evt) {
var $tr = $(this),
$trOther = $tr.data(fcn.counterpart);
$tr.removeClass(fcn.hover);
$trOther.removeClass(fcn.hover);
}
);
$bottomLeftWrapper.on(
"mousewheel DOMMouseScroll",
function (event) {
var $this = $(this),
newScroll = $this.scrollTop(),
scrollUp =
event.originalEvent.wheelDelta > 0 ||
event.originalEvent.detail < 0;
newScroll += scrollUp
? -options.scrollSpeed
: options.scrollSpeed;
$this.scrollTop(newScroll);
$bottomRightWrapper.scrollTop(newScroll);
event.preventDefault();
}
);
$bottomRightWrapper.scroll(function () {
var $this = $(this),
scrollLeft = $this.scrollLeft(),
scrollTop = $this.scrollTop();
$topLeftWrapper
.toggleClass(fcn.scrollBorderBottom, scrollTop > 0)
.toggleClass(fcn.scrollBorderRight, scrollLeft > 0);
$topRightWrapper
.toggleClass(fcn.scrollBorderBottom, scrollTop > 0)
.scrollLeft(scrollLeft);
$bottomLeftWrapper
.toggleClass(fcn.scrollBorderRight, scrollLeft > 0)
.scrollTop(scrollTop);
});
$.ui.fancytree.overrideMethod(
$.ui.fancytree._FancytreeNodeClass.prototype,
"scrollIntoView",
function (effects, options) {
var $prevContainer = tree.$container;
tree.$container = $bottomRightWrapper;
return this._super
.apply(this, arguments)
.always(function () {
tree.$container = $prevContainer;
});
}
);
return res;
},
treeLoad: function (ctx) {
var self = this,
res = this._superApply(arguments);
res.done(function () {
self.ext.fixed._adjustLayout.call(self);
if (self.options.fixed.resizable) {
self.ext.fixed._makeTableResizable();
}
});
return res;
},
_makeTableResizable: function () {
var $wrapper = this.$fixedWrapper,
fcn = this.options.fixed.classNames,
$topLeftWrapper = $wrapper.find("div." + fcn.topLeft),
$topRightWrapper = $wrapper.find("div." + fcn.topRight),
$bottomLeftWrapper = $wrapper.find("div." + fcn.bottomLeft),
$bottomRightWrapper = $wrapper.find("div." + fcn.bottomRight);
function _makeResizable($table) {
$table.resizable({
handles: "e",
resize: function (evt, ui) {
var width = Math.max($table.width(), ui.size.width);
$bottomLeftWrapper.css("width", width);
$topLeftWrapper.css("width", width);
$bottomRightWrapper.css("left", width);
$topRightWrapper.css("left", width);
},
stop: function () {
$table.css("width", "100%");
},
});
}
_makeResizable($topLeftWrapper.find("table"));
_makeResizable($bottomLeftWrapper.find("table"));
},
/* Called by nodeRender to sync node order with tag order.*/
// nodeFixOrder: function(ctx) {
// },
nodeLoadChildren: function (ctx, source) {
return this._superApply(arguments);
},
nodeRemoveChildMarkup: function (ctx) {
var node = ctx.node;
function _removeChild(elem) {
var i,
child,
children = elem.children;
if (children) {
for (i = 0; i < children.length; i++) {
child = children[i];
if (child.trRight) {
$(child.trRight).remove();
}
_removeChild(child);
}
}
}
_removeChild(node);
return this._superApply(arguments);
},
nodeRemoveMarkup: function (ctx) {
var node = ctx.node;
if (node.trRight) {
$(node.trRight).remove();
}
return this._superApply(arguments);
},
nodeSetActive: function (ctx, flag, callOpts) {
var node = ctx.node,
cn = this.options._classNames;
if (node.trRight) {
$(node.trRight)
.toggleClass(cn.active, flag)
.toggleClass(cn.focused, flag);
}
return this._superApply(arguments);
},
nodeKeydown: function (ctx) {
return this._superApply(arguments);
},
nodeSetFocus: function (ctx, flag) {
var node = ctx.node,
cn = this.options._classNames;
if (node.trRight) {
$(node.trRight).toggleClass(cn.focused, flag);
}
return this._superApply(arguments);
},
nodeRender: function (ctx, force, deep, collapsed, _recursive) {
var res = this._superApply(arguments),
node = ctx.node,
isRootNode = !node.parent;
if (!isRootNode && this.$fixedWrapper) {
var $trLeft = $(node.tr),
fcn = this.options.fixed.classNames,
$trRight = $trLeft.data(fcn.counterpart);
if (!$trRight && $trLeft.length) {
var idx = $trLeft.index(),
fixedColCount = this.options.fixed.fixCols,
$blTableBody = this.$fixedWrapper.find(
"div." + fcn.bottomLeft + " table tbody"
),
$brTableBody = this.$fixedWrapper.find(
"div." + fcn.bottomRight + " table tbody"
),
$prevLeftNode = $blTableBody
.find("tr")
.eq(Math.max(idx + 1, 0)),
prevRightNode = $prevLeftNode.data(fcn.counterpart);
$trRight = $trLeft.clone(true);
var trRight = $trRight.get(0);
if (prevRightNode) {
$(prevRightNode).before($trRight);
} else {
$brTableBody.append($trRight);
}
$trRight.show();
trRight.ftnode = node;
node.trRight = trRight;
$trLeft.find("td").slice(fixedColCount).remove();
$trRight.find("td").slice(0, fixedColCount).remove();
$trLeft.data(fcn.counterpart, $trRight);
$trRight.data(fcn.counterpart, $trLeft);
}
}
return res;
},
nodeRenderTitle: function (ctx, title) {
return this._superApply(arguments);
},
nodeRenderStatus: function (ctx) {
var res = this._superApply(arguments),
node = ctx.node;
if (node.trRight) {
var $trRight = $(node.trRight),
$trLeft = $(node.tr),
fcn = this.options.fixed.classNames,
hovering = $trRight.hasClass(fcn.hover),
trClasses = $trLeft.attr("class");
$trRight.attr("class", trClasses);
if (hovering) {
$trRight.addClass(fcn.hover);
$trLeft.addClass(fcn.hover);
}
}
return res;
},
nodeSetExpanded: function (ctx, flag, callOpts) {
var res,
self = this,
node = ctx.node,
$leftTr = $(node.tr),
fcn = this.options.fixed.classNames,
cn = this.options._classNames,
$rightTr = $leftTr.data(fcn.counterpart);
flag = typeof flag === "undefined" ? true : flag;
if (!$rightTr) {
return this._superApply(arguments);
}
$rightTr.toggleClass(cn.expanded, !!flag);
if (flag && !node.isExpanded()) {
res = this._superApply(arguments);
res.done(function () {
node.visit(function (child) {
var $trLeft = $(child.tr),
$trRight = $trLeft.data(fcn.counterpart);
self.ext.fixed._adjustRowHeight($trLeft, $trRight);
if (!child.expanded) {
return "skip";
}
});
self.ext.fixed._adjustColWidths();
self.ext.fixed._adjustWrapperLayout();
});
} else if (!flag && node.isExpanded()) {
node.visit(function (child) {
var $trLeft = $(child.tr),
$trRight = $trLeft.data(fcn.counterpart);
if ($trRight) {
if (!child.expanded) {
return "skip";
}
}
});
self.ext.fixed._adjustColWidths();
self.ext.fixed._adjustWrapperLayout();
res = this._superApply(arguments);
} else {
res = this._superApply(arguments);
}
return res;
},
nodeSetStatus: function (ctx, status, message, details) {
return this._superApply(arguments);
},
treeClear: function (ctx) {
var tree = ctx.tree,
$table = tree.widget.element,
$wrapper = this.$fixedWrapper,
fcn = this.options.fixed.classNames;
$table.find("tr, td, th, thead").removeClass(fcn.hidden).css({
"min-width": "auto",
height: "auto",
});
$wrapper.empty().append($table);
return this._superApply(arguments);
},
treeRegisterNode: function (ctx, add, node) {
return this._superApply(arguments);
},
treeDestroy: function (ctx) {
var tree = ctx.tree,
$table = tree.widget.element,
$wrapper = this.$fixedWrapper,
fcn = this.options.fixed.classNames;
$table.find("tr, td, th, thead").removeClass(fcn.hidden).css({
"min-width": "auto",
height: "auto",
});
$wrapper.empty().append($table);
return this._superApply(arguments);
},
_adjustColWidths: function () {
if (this.options.fixed.adjustColWidths) {
this.options.fixed.adjustColWidths.call(this);
return;
}
var $wrapper = this.$fixedWrapper,
fcn = this.options.fixed.classNames,
$tlWrapper = $wrapper.find("div." + fcn.topLeft),
$blWrapper = $wrapper.find("div." + fcn.bottomLeft),
$trWrapper = $wrapper.find("div." + fcn.topRight),
$brWrapper = $wrapper.find("div." + fcn.bottomRight);
function _adjust($topWrapper, $bottomWrapper) {
var $trTop = $topWrapper.find("thead tr").first(),
$trBottom = $bottomWrapper.find("tbody tr").first();
$trTop.find("th").each(function (idx) {
var $thTop = $(this),
$tdBottom = $trBottom.find("td").eq(idx),
thTopWidth = $thTop.width(),
thTopOuterWidth = $thTop.outerWidth(),
tdBottomWidth = $tdBottom.width(),
tdBottomOuterWidth = $tdBottom.outerWidth(),
newWidth = Math.max(
thTopOuterWidth,
tdBottomOuterWidth
);
$thTop.css(
"min-width",
newWidth - (thTopOuterWidth - thTopWidth)
);
$tdBottom.css(
"min-width",
newWidth - (tdBottomOuterWidth - tdBottomWidth)
);
});
}
_adjust($tlWrapper, $blWrapper);
_adjust($trWrapper, $brWrapper);
},
_adjustRowHeight: function ($tr1, $tr2) {
var fcn = this.options.fixed.classNames;
if (!$tr2) {
$tr2 = $tr1.data(fcn.counterpart);
}
$tr1.css("height", "auto");
$tr2.css("height", "auto");
var row1Height = $tr1.outerHeight(),
row2Height = $tr2.outerHeight(),
newHeight = Math.max(row1Height, row2Height);
$tr1.css("height", newHeight + 1);
$tr2.css("height", newHeight + 1);
},
_adjustWrapperLayout: function () {
var $wrapper = this.$fixedWrapper,
fcn = this.options.fixed.classNames,
$topLeftWrapper = $wrapper.find("div." + fcn.topLeft),
$topRightWrapper = $wrapper.find("div." + fcn.topRight),
$bottomLeftWrapper = $wrapper.find("div." + fcn.bottomLeft),
$bottomRightWrapper = $wrapper.find("div." + fcn.bottomRight),
$topLeftTable = $topLeftWrapper.find("table"),
$topRightTable = $topRightWrapper.find("table"),
// $bottomLeftTable = $bottomLeftWrapper.find("table"),
wrapperWidth = $wrapper.width(),
wrapperHeight = $wrapper.height(),
fixedWidth = Math.min(wrapperWidth, $topLeftTable.width()),
fixedHeight = Math.min(
wrapperHeight,
Math.max($topLeftTable.height(), $topRightTable.height())
);
// vScrollbar = $bottomRightWrapper.get(0).scrollHeight > (wrapperHeight - fixedHeight),
// hScrollbar = $bottomRightWrapper.get(0).scrollWidth > (wrapperWidth - fixedWidth);
$topLeftWrapper.css({
width: fixedWidth,
height: fixedHeight,
});
$topRightWrapper.css({
// width: wrapperWidth - fixedWidth - (vScrollbar ? 17 : 0),
// width: "calc(100% - " + (fixedWidth + (vScrollbar ? 17 : 0)) + "px)",
width: "calc(100% - " + (fixedWidth + 17) + "px)",
height: fixedHeight,
left: fixedWidth,
});
$bottomLeftWrapper.css({
width: fixedWidth,
// height: vScrollbar ? wrapperHeight - fixedHeight - (hScrollbar ? 17 : 0) : "auto",
// height: vScrollbar ? ("calc(100% - " + (fixedHeight + (hScrollbar ? 17 : 0)) + "px)") : "auto",
// height: vScrollbar ? ("calc(100% - " + (fixedHeight + 17) + "px)") : "auto",
height: "calc(100% - " + (fixedHeight + 17) + "px)",
top: fixedHeight,
});
$bottomRightWrapper.css({
// width: wrapperWidth - fixedWidth,
// height: vScrollbar ? wrapperHeight - fixedHeight : "auto",
width: "calc(100% - " + fixedWidth + "px)",
// height: vScrollbar ? ("calc(100% - " + fixedHeight + "px)") : "auto",
height: "calc(100% - " + fixedHeight + "px)",
top: fixedHeight,
left: fixedWidth,
});
},
_adjustLayout: function () {
var self = this,
$wrapper = this.$fixedWrapper,
fcn = this.options.fixed.classNames,
$topLeftWrapper = $wrapper.find("div." + fcn.topLeft),
$topRightWrapper = $wrapper.find("div." + fcn.topRight),
$bottomLeftWrapper = $wrapper.find("div." + fcn.bottomLeft);
// $bottomRightWrapper = $wrapper.find("div." + fcn.bottomRight)
$topLeftWrapper.find("table tr").each(function (idx) {
var $trRight = $topRightWrapper.find("tr").eq(idx);
self.ext.fixed._adjustRowHeight($(this), $trRight);
});
$bottomLeftWrapper
.find("table tbody")
.find("tr")
.each(function (idx) {
// var $trRight = $bottomRightWrapper.find("tbody").find("tr").eq(idx);
self.ext.fixed._adjustRowHeight($(this));
});
self.ext.fixed._adjustColWidths.call(this);
self.ext.fixed._adjustWrapperLayout.call(this);
},
// treeSetFocus: function(ctx, flag) {
//// alert("treeSetFocus" + ctx.tree.$container);
// ctx.tree.$container.focus();
// $.ui.fancytree.focusTree = ctx.tree;
// }
});
// Value returned by `require('jquery.fancytree..')`
return $.ui.fancytree;
}); // End of closure

View file

@ -0,0 +1,354 @@
/*!
* jquery.fancytree.glyph.js
*
* Use glyph-fonts, ligature-fonts, or SVG icons instead of icon sprites.
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
*
* Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
*
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.38.3
* @date 2023-02-01T20:52:50Z
*/
(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";
/******************************************************************************
* Private functions and variables
*/
var FT = $.ui.fancytree,
PRESETS = {
awesome3: {
// Outdated!
_addClass: "",
checkbox: "icon-check-empty",
checkboxSelected: "icon-check",
checkboxUnknown: "icon-check icon-muted",
dragHelper: "icon-caret-right",
dropMarker: "icon-caret-right",
error: "icon-exclamation-sign",
expanderClosed: "icon-caret-right",
expanderLazy: "icon-angle-right",
expanderOpen: "icon-caret-down",
loading: "icon-refresh icon-spin",
nodata: "icon-meh",
noExpander: "",
radio: "icon-circle-blank",
radioSelected: "icon-circle",
// radioUnknown: "icon-circle icon-muted",
// Default node icons.
// (Use tree.options.icon callback to define custom icons based on node data)
doc: "icon-file-alt",
docOpen: "icon-file-alt",
folder: "icon-folder-close-alt",
folderOpen: "icon-folder-open-alt",
},
awesome4: {
_addClass: "fa",
checkbox: "fa-square-o",
checkboxSelected: "fa-check-square-o",
checkboxUnknown: "fa-square fancytree-helper-indeterminate-cb",
dragHelper: "fa-arrow-right",
dropMarker: "fa-long-arrow-right",
error: "fa-warning",
expanderClosed: "fa-caret-right",
expanderLazy: "fa-angle-right",
expanderOpen: "fa-caret-down",
// We may prevent wobbling rotations on FF by creating a separate sub element:
loading: { html: "<span class='fa fa-spinner fa-pulse' />" },
nodata: "fa-meh-o",
noExpander: "",
radio: "fa-circle-thin", // "fa-circle-o"
radioSelected: "fa-circle",
// radioUnknown: "fa-dot-circle-o",
// Default node icons.
// (Use tree.options.icon callback to define custom icons based on node data)
doc: "fa-file-o",
docOpen: "fa-file-o",
folder: "fa-folder-o",
folderOpen: "fa-folder-open-o",
},
awesome5: {
// fontawesome 5 have several different base classes
// "far, fas, fal and fab" The rendered svg puts that prefix
// in a different location so we have to keep them separate here
_addClass: "",
checkbox: "far fa-square",
checkboxSelected: "far fa-check-square",
// checkboxUnknown: "far fa-window-close",
checkboxUnknown:
"fas fa-square fancytree-helper-indeterminate-cb",
radio: "far fa-circle",
radioSelected: "fas fa-circle",
radioUnknown: "far fa-dot-circle",
dragHelper: "fas fa-arrow-right",
dropMarker: "fas fa-long-arrow-alt-right",
error: "fas fa-exclamation-triangle",
expanderClosed: "fas fa-caret-right",
expanderLazy: "fas fa-angle-right",
expanderOpen: "fas fa-caret-down",
loading: "fas fa-spinner fa-pulse",
nodata: "far fa-meh",
noExpander: "",
// Default node icons.
// (Use tree.options.icon callback to define custom icons based on node data)
doc: "far fa-file",
docOpen: "far fa-file",
folder: "far fa-folder",
folderOpen: "far fa-folder-open",
},
bootstrap3: {
_addClass: "glyphicon",
checkbox: "glyphicon-unchecked",
checkboxSelected: "glyphicon-check",
checkboxUnknown:
"glyphicon-expand fancytree-helper-indeterminate-cb", // "glyphicon-share",
dragHelper: "glyphicon-play",
dropMarker: "glyphicon-arrow-right",
error: "glyphicon-warning-sign",
expanderClosed: "glyphicon-menu-right", // glyphicon-plus-sign
expanderLazy: "glyphicon-menu-right", // glyphicon-plus-sign
expanderOpen: "glyphicon-menu-down", // glyphicon-minus-sign
loading: "glyphicon-refresh fancytree-helper-spin",
nodata: "glyphicon-info-sign",
noExpander: "",
radio: "glyphicon-remove-circle", // "glyphicon-unchecked",
radioSelected: "glyphicon-ok-circle", // "glyphicon-check",
// radioUnknown: "glyphicon-ban-circle",
// Default node icons.
// (Use tree.options.icon callback to define custom icons based on node data)
doc: "glyphicon-file",
docOpen: "glyphicon-file",
folder: "glyphicon-folder-close",
folderOpen: "glyphicon-folder-open",
},
material: {
_addClass: "material-icons",
checkbox: { text: "check_box_outline_blank" },
checkboxSelected: { text: "check_box" },
checkboxUnknown: { text: "indeterminate_check_box" },
dragHelper: { text: "play_arrow" },
dropMarker: { text: "arrow-forward" },
error: { text: "warning" },
expanderClosed: { text: "chevron_right" },
expanderLazy: { text: "last_page" },
expanderOpen: { text: "expand_more" },
loading: {
text: "autorenew",
addClass: "fancytree-helper-spin",
},
nodata: { text: "info" },
noExpander: { text: "" },
radio: { text: "radio_button_unchecked" },
radioSelected: { text: "radio_button_checked" },
// Default node icons.
// (Use tree.options.icon callback to define custom icons based on node data)
doc: { text: "insert_drive_file" },
docOpen: { text: "insert_drive_file" },
folder: { text: "folder" },
folderOpen: { text: "folder_open" },
},
};
function setIcon(node, span, baseClass, opts, type) {
var map = opts.map,
icon = map[type],
$span = $(span),
$counter = $span.find(".fancytree-childcounter"),
setClass = baseClass + " " + (map._addClass || "");
// #871 Allow a callback
if (typeof icon === "function") {
icon = icon.call(this, node, span, type);
}
// node.debug( "setIcon(" + baseClass + ", " + type + "): " + "oldIcon" + " -> " + icon );
// #871: propsed this, but I am not sure how robust this is, e.g.
// the prefix (fas, far) class changes are not considered?
// if (span.tagName === "svg" && opts.preset === "awesome5") {
// // fa5 script converts <i> to <svg> so call a specific handler.
// var oldIcon = "fa-" + $span.data("icon");
// // node.debug( "setIcon(" + baseClass + ", " + type + "): " + oldIcon + " -> " + icon );
// if (typeof oldIcon === "string") {
// $span.removeClass(oldIcon);
// }
// if (typeof icon === "string") {
// $span.addClass(icon);
// }
// return;
// }
if (typeof icon === "string") {
// #883: remove inner html that may be added by prev. mode
span.innerHTML = "";
$span.attr("class", setClass + " " + icon).append($counter);
} else if (icon) {
if (icon.text) {
span.textContent = "" + icon.text;
} else if (icon.html) {
span.innerHTML = icon.html;
} else {
span.innerHTML = "";
}
$span
.attr("class", setClass + " " + (icon.addClass || ""))
.append($counter);
}
}
$.ui.fancytree.registerExtension({
name: "glyph",
version: "2.38.3",
// Default options for this extension.
options: {
preset: null, // 'awesome3', 'awesome4', 'bootstrap3', 'material'
map: {},
},
treeInit: function (ctx) {
var tree = ctx.tree,
opts = ctx.options.glyph;
if (opts.preset) {
FT.assert(
!!PRESETS[opts.preset],
"Invalid value for `options.glyph.preset`: " + opts.preset
);
opts.map = $.extend({}, PRESETS[opts.preset], opts.map);
} else {
tree.warn("ext-glyph: missing `preset` option.");
}
this._superApply(arguments);
tree.$container.addClass("fancytree-ext-glyph");
},
nodeRenderStatus: function (ctx) {
var checkbox,
icon,
res,
span,
node = ctx.node,
$span = $(node.span),
opts = ctx.options.glyph;
res = this._super(ctx);
if (node.isRootNode()) {
return res;
}
span = $span.children(".fancytree-expander").get(0);
if (span) {
// if( node.isLoading() ){
// icon = "loading";
if (node.expanded && node.hasChildren()) {
icon = "expanderOpen";
} else if (node.isUndefined()) {
icon = "expanderLazy";
} else if (node.hasChildren()) {
icon = "expanderClosed";
} else {
icon = "noExpander";
}
// span.className = "fancytree-expander " + map[icon];
setIcon(node, span, "fancytree-expander", opts, icon);
}
if (node.tr) {
span = $("td", node.tr).find(".fancytree-checkbox").get(0);
} else {
span = $span.children(".fancytree-checkbox").get(0);
}
if (span) {
checkbox = FT.evalOption("checkbox", node, node, opts, false);
if (
(node.parent && node.parent.radiogroup) ||
checkbox === "radio"
) {
icon = node.selected ? "radioSelected" : "radio";
setIcon(
node,
span,
"fancytree-checkbox fancytree-radio",
opts,
icon
);
} else {
// eslint-disable-next-line no-nested-ternary
icon = node.selected
? "checkboxSelected"
: node.partsel
? "checkboxUnknown"
: "checkbox";
// span.className = "fancytree-checkbox " + map[icon];
setIcon(node, span, "fancytree-checkbox", opts, icon);
}
}
// Standard icon (note that this does not match .fancytree-custom-icon,
// that might be set by opts.icon callbacks)
span = $span.children(".fancytree-icon").get(0);
if (span) {
if (node.statusNodeType) {
icon = node.statusNodeType; // loading, error
} else if (node.folder) {
icon =
node.expanded && node.hasChildren()
? "folderOpen"
: "folder";
} else {
icon = node.expanded ? "docOpen" : "doc";
}
setIcon(node, span, "fancytree-icon", opts, icon);
}
return res;
},
nodeSetStatus: function (ctx, status, message, details) {
var res,
span,
opts = ctx.options.glyph,
node = ctx.node;
res = this._superApply(arguments);
if (
status === "error" ||
status === "loading" ||
status === "nodata"
) {
if (node.parent) {
span = $(".fancytree-expander", node.span).get(0);
if (span) {
setIcon(node, span, "fancytree-expander", opts, status);
}
} else {
//
span = $(
".fancytree-statusnode-" + status,
node[this.nodeContainerAttrName]
)
.find(".fancytree-icon")
.get(0);
if (span) {
setIcon(node, span, "fancytree-icon", opts, status);
}
}
}
return res;
},
});
// Value returned by `require('jquery.fancytree..')`
return $.ui.fancytree;
}); // End of closure

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,218 @@
/*!
* jquery.fancytree.gridnav.js
*
* Support keyboard navigation for trees with embedded input controls.
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
*
* Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
*
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.38.3
* @date 2023-02-01T20:52:50Z
*/
(function (factory) {
if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
define([
"jquery",
"./jquery.fancytree",
"./jquery.fancytree.table",
], factory);
} else if (typeof module === "object" && module.exports) {
// Node/CommonJS
require("./jquery.fancytree.table"); // core + table
module.exports = factory(require("jquery"));
} else {
// Browser globals
factory(jQuery);
}
})(function ($) {
"use strict";
/*******************************************************************************
* Private functions and variables
*/
// Allow these navigation keys even when input controls are focused
var KC = $.ui.keyCode,
// which keys are *not* handled by embedded control, but passed to tree
// navigation handler:
NAV_KEYS = {
text: [KC.UP, KC.DOWN],
checkbox: [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
link: [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
radiobutton: [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
"select-one": [KC.LEFT, KC.RIGHT],
"select-multiple": [KC.LEFT, KC.RIGHT],
};
/* Calculate TD column index (considering colspans).*/
function getColIdx($tr, $td) {
var colspan,
td = $td.get(0),
idx = 0;
$tr.children().each(function () {
if (this === td) {
return false;
}
colspan = $(this).prop("colspan");
idx += colspan ? colspan : 1;
});
return idx;
}
/* Find TD at given column index (considering colspans).*/
function findTdAtColIdx($tr, colIdx) {
var colspan,
res = null,
idx = 0;
$tr.children().each(function () {
if (idx >= colIdx) {
res = $(this);
return false;
}
colspan = $(this).prop("colspan");
idx += colspan ? colspan : 1;
});
return res;
}
/* Find adjacent cell for a given direction. Skip empty cells and consider merged cells */
function findNeighbourTd($target, keyCode) {
var $tr,
colIdx,
$td = $target.closest("td"),
$tdNext = null;
switch (keyCode) {
case KC.LEFT:
$tdNext = $td.prev();
break;
case KC.RIGHT:
$tdNext = $td.next();
break;
case KC.UP:
case KC.DOWN:
$tr = $td.parent();
colIdx = getColIdx($tr, $td);
while (true) {
$tr = keyCode === KC.UP ? $tr.prev() : $tr.next();
if (!$tr.length) {
break;
}
// Skip hidden rows
if ($tr.is(":hidden")) {
continue;
}
// Find adjacent cell in the same column
$tdNext = findTdAtColIdx($tr, colIdx);
// Skip cells that don't conatain a focusable element
if ($tdNext && $tdNext.find(":input,a").length) {
break;
}
}
break;
}
return $tdNext;
}
/*******************************************************************************
* Extension code
*/
$.ui.fancytree.registerExtension({
name: "gridnav",
version: "2.38.3",
// Default options for this extension.
options: {
autofocusInput: false, // Focus first embedded input if node gets activated
handleCursorKeys: true, // Allow UP/DOWN in inputs to move to prev/next node
},
treeInit: function (ctx) {
// gridnav requires the table extension to be loaded before itself
this._requireExtension("table", true, true);
this._superApply(arguments);
this.$container.addClass("fancytree-ext-gridnav");
// Activate node if embedded input gets focus (due to a click)
this.$container.on("focusin", function (event) {
var ctx2,
node = $.ui.fancytree.getNode(event.target);
if (node && !node.isActive()) {
// Call node.setActive(), but also pass the event
ctx2 = ctx.tree._makeHookContext(node, event);
ctx.tree._callHook("nodeSetActive", ctx2, true);
}
});
},
nodeSetActive: function (ctx, flag, callOpts) {
var $outer,
opts = ctx.options.gridnav,
node = ctx.node,
event = ctx.originalEvent || {},
triggeredByInput = $(event.target).is(":input");
flag = flag !== false;
this._superApply(arguments);
if (flag) {
if (ctx.options.titlesTabbable) {
if (!triggeredByInput) {
$(node.span).find("span.fancytree-title").focus();
node.setFocus();
}
// If one node is tabbable, the container no longer needs to be
ctx.tree.$container.attr("tabindex", "-1");
// ctx.tree.$container.removeAttr("tabindex");
} else if (opts.autofocusInput && !triggeredByInput) {
// Set focus to input sub input (if node was clicked, but not
// when TAB was pressed )
$outer = $(node.tr || node.span);
$outer.find(":input:enabled").first().focus();
}
}
},
nodeKeydown: function (ctx) {
var inputType,
handleKeys,
$td,
opts = ctx.options.gridnav,
event = ctx.originalEvent,
$target = $(event.target);
if ($target.is(":input:enabled")) {
inputType = $target.prop("type");
} else if ($target.is("a")) {
inputType = "link";
}
// ctx.tree.debug("ext-gridnav nodeKeydown", event, inputType);
if (inputType && opts.handleCursorKeys) {
handleKeys = NAV_KEYS[inputType];
if (handleKeys && $.inArray(event.which, handleKeys) >= 0) {
$td = findNeighbourTd($target, event.which);
if ($td && $td.length) {
// ctx.node.debug("ignore keydown in input", event.which, handleKeys);
$td.find(":input:enabled,a").focus();
// Prevent Fancytree default navigation
return false;
}
}
return true;
}
// ctx.tree.debug("ext-gridnav NOT HANDLED", event, inputType);
return this._superApply(arguments);
},
});
// Value returned by `require('jquery.fancytree..')`
return $.ui.fancytree;
}); // End of closure

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,309 @@
/*!
* jquery.fancytree.logger.js
*
* Miscellaneous debug extensions.
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
*
* Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
*
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.38.3
* @date 2023-02-01T20:52:50Z
*/
(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";
/******************************************************************************
* Private functions and variables
*/
var i,
FT = $.ui.fancytree,
PREFIX = "ft-logger: ",
logLine = window.console.log,
// HOOK_NAMES = "nodeClick nodeCollapseSiblings".split(" "),
TREE_EVENT_NAMES =
"beforeRestore beforeUpdateViewport blurTree create init focusTree preInit restore updateViewport".split(
" "
),
NODE_EVENT_NAMES =
"activate activateCell beforeActivate beforeExpand beforeSelect blur click collapse createNode dblclick deactivate defaultGridAction expand enhanceTitle focus keydown keypress lazyLoad loadChildren loadError modifyChild postProcess renderNode renderTitle select".split(
" "
),
EVENT_NAMES = TREE_EVENT_NAMES.concat(NODE_EVENT_NAMES),
// HOOK_NAME_MAP = {},
EVENT_NAME_MAP = {};
/*
*/
// for (i = 0; i < HOOK_NAMES.length; i++) {
// HOOK_NAME_MAP[HOOK_NAMES[i]] = true;
// }
for (i = 0; i < EVENT_NAMES.length; i++) {
EVENT_NAME_MAP[EVENT_NAMES[i]] = true;
}
function getBrowserInfo() {
var n = navigator.appName,
ua = navigator.userAgent,
tem,
m = ua.match(
/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i
);
if (m && (tem = ua.match(/version\/([.\d]+)/i)) !== null) {
m[2] = tem[1];
}
m = m ? [m[1], m[2]] : [n, navigator.appVersion, "-?"];
return m.join(", ");
}
function logEvent(event, data) {
var res,
self = this,
// logName = PREFIX + "event." + event.type,
opts = data.options.logger,
tree = data.tree,
// widget = data.widget,
obj = data.node || tree,
logName = PREFIX + "event." + event.type + " (" + obj + ")";
if (
!opts.traceEvents ||
(opts.traceEvents !== true && $.inArray(name, opts.traceEvents) < 0)
) {
return self._super.apply(self, arguments);
}
if (
(self._super && opts.timings === true) ||
(opts.timings && $.inArray(name, opts.timings) >= 0)
) {
// if( name === "nodeRender" ) { logName += obj; } // allow timing for recursive calls
// logName += " (" + obj + ")";
window.console.time(logName);
res = self._super.apply(self, arguments);
window.console.timeEnd(logName);
} else {
// obj.info(logName, data);
logLine(logName, event, data);
res = self._super.apply(self, arguments);
}
return res;
}
function logHook(name, this_, args, extra) {
var res,
ctx = args[0],
opts = ctx.options.logger,
obj = ctx.node || ctx.tree,
logName = PREFIX + "hook." + name + " (" + obj + ")";
if (
!opts.traceHooks ||
(opts.traceHooks !== true && $.inArray(name, opts.traceHooks) < 0)
) {
return this_._superApply.call(this_, args);
}
if (
opts.timings === true ||
(opts.timings && $.inArray(name, opts.timings) >= 0)
) {
// if( name === "nodeRender" ) { logName += obj; } // allow timing for recursive calls
// logName += " (" + obj + ")";
window.console.time(logName);
res = this_._superApply.call(this_, args);
window.console.timeEnd(logName);
} else {
if (extra) {
// obj.info(logName, extra, ctx);
logLine(logName, extra, ctx);
} else {
// obj.info(logName, ctx);
logLine(logName, ctx);
}
res = this_._superApply.call(this_, args);
}
return res;
}
/******************************************************************************
* Extension code
*/
$.ui.fancytree.registerExtension({
name: "logger",
version: "2.38.3",
// Default options for this extension.
options: {
logTarget: null, // optional redirect logging to this <div> tag
traceEvents: true, // `true`or list of hook names
traceUnhandledEvents: false,
traceHooks: false, // `true`or list of event names
timings: false, // `true`or list of event names
},
// Overide virtual methods for this extension.
// `this` : is this Fancytree object
// `this._super`: the virtual function that was overridden (member of prev. extension or Fancytree)
treeCreate: function (ctx) {
var tree = ctx.tree,
opts = ctx.options;
if (
this.options.extensions[this.options.extensions.length - 1] !==
"logger"
) {
throw Error(
"Fancytree 'logger' extension must be listed as last entry."
);
}
tree.warn(
"Fancytree logger extension is enabled (this may be slow).",
opts.logger
);
tree.debug(
"Fancytree v" +
$.ui.fancytree.version +
", buildType='" +
$.ui.fancytree.buildType +
"'"
);
tree.debug(
"jQuery UI " +
jQuery.ui.version +
" (uiBackCompat=" +
$.uiBackCompat +
")"
);
tree.debug("jQuery " + jQuery.fn.jquery);
tree.debug("Browser: " + getBrowserInfo());
function _log(event, data) {
logLine(
PREFIX + "event." + event.type + " (unhandled)",
event,
data
);
}
$.each(EVENT_NAMES, function (i, name) {
if (typeof opts[name] === "function") {
// tree.info(PREFIX + "override '" + name + "' event");
$.ui.fancytree.overrideMethod(
opts,
name,
logEvent,
ctx.widget
);
} else if (opts.logger.traceUnhandledEvents) {
opts[name] = _log;
}
});
return logHook("treeCreate", this, arguments);
},
nodeClick: function (ctx) {
return logHook(
"nodeClick",
this,
arguments,
FT.eventToString(ctx.originalEvent)
);
},
nodeCollapseSiblings: function (ctx) {
return logHook("nodeCollapseSiblings", this, arguments);
},
nodeDblclick: function (ctx) {
return logHook("nodeDblclick", this, arguments);
},
nodeKeydown: function (ctx) {
return logHook(
"nodeKeydown",
this,
arguments,
FT.eventToString(ctx.originalEvent)
);
},
nodeLoadChildren: function (ctx, source) {
return logHook("nodeLoadChildren", this, arguments);
},
nodeRemoveChildMarkup: function (ctx) {
return logHook("nodeRemoveChildMarkup", this, arguments);
},
nodeRemoveMarkup: function (ctx) {
return logHook("nodeRemoveMarkup", this, arguments);
},
nodeRender: function (ctx, force, deep, collapsed, _recursive) {
return logHook("nodeRender", this, arguments);
},
nodeRenderStatus: function (ctx) {
return logHook("nodeRenderStatus", this, arguments);
},
nodeRenderTitle: function (ctx, title) {
return logHook("nodeRenderTitle", this, arguments);
},
nodeSetActive: function (ctx, flag, callOpts) {
return logHook("nodeSetActive", this, arguments);
},
nodeSetExpanded: function (ctx, flag, callOpts) {
return logHook("nodeSetExpanded", this, arguments);
},
nodeSetFocus: function (ctx) {
return logHook("nodeSetFocus", this, arguments);
},
nodeSetSelected: function (ctx, flag, callOpts) {
return logHook("nodeSetSelected", this, arguments);
},
nodeSetStatus: function (ctx, status, message, details) {
return logHook("nodeSetStatus", this, arguments);
},
nodeToggleExpanded: function (ctx) {
return logHook("nodeToggleExpanded", this, arguments);
},
nodeToggleSelected: function (ctx) {
return logHook("nodeToggleSelected", this, arguments);
},
treeClear: function (ctx) {
return logHook("treeClear", this, arguments);
},
// treeCreate: function(ctx) {
// return logHook("treeCreate", this, arguments);
// },
treeDestroy: function (ctx) {
return logHook("treeDestroy", this, arguments);
},
treeInit: function (ctx) {
return logHook("treeInit", this, arguments);
},
treeLoad: function (ctx, source) {
return logHook("treeLoad", this, arguments);
},
treeRegisterNode: function (ctx, add, node) {
return logHook("treeRegisterNode", this, arguments);
},
treeSetFocus: function (ctx, flag, callOpts) {
return logHook("treeSetFocus", this, arguments);
},
treeSetOption: function (ctx, key, value) {
return logHook("treeSetOption", this, arguments);
},
treeStructureChanged: function (ctx, type) {
return logHook("treeStructureChanged", this, arguments);
},
});
// Value returned by `require('jquery.fancytree..')`
return $.ui.fancytree;
}); // End of closure

View file

@ -0,0 +1,185 @@
/*!
* jquery.fancytree.menu.js
*
* Enable jQuery UI Menu as context menu for tree nodes.
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
*
* @see http://api.jqueryui.com/menu/
*
* Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
*
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.38.3
* @date 2023-02-01T20:52:50Z
*/
(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";
$.ui.fancytree.registerExtension({
name: "menu",
version: "2.38.3",
// Default options for this extension.
options: {
enable: true,
selector: null, //
position: {}, //
// Events:
create: $.noop, //
beforeOpen: $.noop, //
open: $.noop, //
focus: $.noop, //
select: $.noop, //
close: $.noop, //
},
// Override virtual methods for this extension.
// `this` : is this extension object
// `this._base` : the Fancytree instance
// `this._super`: the virtual function that was overridden (member of prev. extension or Fancytree)
treeInit: function (ctx) {
var opts = ctx.options,
tree = ctx.tree;
this._superApply(arguments);
// Prepare an object that will be passed with menu events
tree.ext.menu.data = {
tree: tree,
node: null,
$menu: null,
menuId: null,
};
// tree.$container[0].oncontextmenu = function() {return false;};
// Replace the standard browser context menu with out own
tree.$container.on(
"contextmenu",
"span.fancytree-node",
function (event) {
var node = $.ui.fancytree.getNode(event),
ctx = {
node: node,
tree: node.tree,
originalEvent: event,
options: tree.options,
};
tree.ext.menu._openMenu(ctx);
return false;
}
);
// Use jquery.ui.menu
$(opts.menu.selector)
.menu({
create: function (event, ui) {
tree.ext.menu.data.$menu = $(this).menu("widget");
var data = $.extend({}, tree.ext.menu.data);
opts.menu.create.call(tree, event, data);
},
focus: function (event, ui) {
var data = $.extend({}, tree.ext.menu.data, {
menuItem: ui.item,
menuId: ui.item.find(">a").attr("href"),
});
opts.menu.focus.call(tree, event, data);
},
select: function (event, ui) {
var data = $.extend({}, tree.ext.menu.data, {
menuItem: ui.item,
menuId: ui.item.find(">a").attr("href"),
});
if (
opts.menu.select.call(tree, event, data) !== false
) {
tree.ext.menu._closeMenu(ctx);
}
},
})
.hide();
},
treeDestroy: function (ctx) {
this._superApply(arguments);
},
_openMenu: function (ctx) {
var data,
tree = ctx.tree,
opts = ctx.options,
$menu = $(opts.menu.selector);
tree.ext.menu.data.node = ctx.node;
data = $.extend({}, tree.ext.menu.data);
if (
opts.menu.beforeOpen.call(tree, ctx.originalEvent, data) ===
false
) {
return;
}
$(document)
.on("keydown.fancytree", function (event) {
if (event.which === $.ui.keyCode.ESCAPE) {
tree.ext.menu._closeMenu(ctx);
}
})
.on("mousedown.fancytree", function (event) {
// Close menu when clicked outside menu
if ($(event.target).closest(".ui-menu-item").length === 0) {
tree.ext.menu._closeMenu(ctx);
}
});
// $menu.position($.extend({my: "left top", at: "left bottom", of: event}, opts.menu.position));
$menu
.css("position", "absolute")
.show()
.position({
my: "left top",
at: "right top",
of: ctx.originalEvent,
collision: "fit",
})
.focus();
opts.menu.open.call(tree, ctx.originalEvent, data);
},
_closeMenu: function (ctx) {
var $menu,
tree = ctx.tree,
opts = ctx.options,
data = $.extend({}, tree.ext.menu.data);
if (opts.menu.close.call(tree, ctx.originalEvent, data) === false) {
return;
}
$menu = $(opts.menu.selector);
$(document).off("keydown.fancytree, mousedown.fancytree");
$menu.hide();
tree.ext.menu.data.node = null;
},
// ,
// nodeClick: function(ctx) {
// var event = ctx.originalEvent;
// if(event.which === 2 || (event.which === 1 && event.ctrlKey)){
// event.preventDefault();
// ctx.tree.ext.menu._openMenu(ctx);
// return false;
// }
// this._superApply(arguments);
// }
});
// Value returned by `require('jquery.fancytree..')`
return $.ui.fancytree;
}); // End of closure

View file

@ -0,0 +1,128 @@
/*!
* jquery.fancytree.multi.js
*
* Allow multiple selection of nodes by mouse or keyboard.
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
*
* Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
*
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.38.3
* @date 2023-02-01T20:52:50Z
*/
(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";
/*******************************************************************************
* Private functions and variables
*/
// var isMac = /Mac/.test(navigator.platform);
/*******************************************************************************
* Extension code
*/
$.ui.fancytree.registerExtension({
name: "multi",
version: "2.38.3",
// Default options for this extension.
options: {
allowNoSelect: false, //
mode: "sameParent", //
// Events:
// beforeSelect: $.noop // Return false to prevent cancel/save (data.input is available)
},
treeInit: function (ctx) {
this._superApply(arguments);
this.$container.addClass("fancytree-ext-multi");
if (ctx.options.selectMode === 1) {
$.error(
"Fancytree ext-multi: selectMode: 1 (single) is not compatible."
);
}
},
nodeClick: function (ctx) {
var //pluginOpts = ctx.options.multi,
tree = ctx.tree,
node = ctx.node,
activeNode = tree.getActiveNode() || tree.getFirstChild(),
isCbClick = ctx.targetType === "checkbox",
isExpanderClick = ctx.targetType === "expander",
eventStr = $.ui.fancytree.eventToString(ctx.originalEvent);
switch (eventStr) {
case "click":
if (isExpanderClick) {
break;
} // Default handler will expand/collapse
if (!isCbClick) {
tree.selectAll(false);
// Select clicked node (radio-button mode)
node.setSelected();
}
// Default handler will toggle checkbox clicks and activate
break;
case "shift+click":
// node.debug("click")
tree.visitRows(
function (n) {
// n.debug("click2", n===node, node)
n.setSelected();
if (n === node) {
return false;
}
},
{
start: activeNode,
reverse: activeNode.isBelowOf(node),
}
);
break;
case "ctrl+click":
case "meta+click": // Mac: [Command]
node.toggleSelected();
return;
}
return this._superApply(arguments);
},
nodeKeydown: function (ctx) {
var tree = ctx.tree,
node = ctx.node,
event = ctx.originalEvent,
eventStr = $.ui.fancytree.eventToString(event);
switch (eventStr) {
case "up":
case "down":
tree.selectAll(false);
node.navigate(event.which, true);
tree.getActiveNode().setSelected();
break;
case "shift+up":
case "shift+down":
node.navigate(event.which, true);
tree.getActiveNode().setSelected();
break;
}
return this._superApply(arguments);
},
});
// Value returned by `require('jquery.fancytree..')`
return $.ui.fancytree;
}); // End of closure

View file

@ -0,0 +1,503 @@
/*!
* 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 2.38.3
* @date 2023-02-01T20:52:50Z
*/
(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: "2.38.3",
// 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

View file

@ -0,0 +1,544 @@
/*!
* jquery.fancytree.table.js
*
* Render tree as table (aka 'tree grid', 'table tree').
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
*
* Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
*
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.38.3
* @date 2023-02-01T20:52:50Z
*/
(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";
/******************************************************************************
* Private functions and variables
*/
var _assert = $.ui.fancytree.assert;
function insertFirstChild(referenceNode, newNode) {
referenceNode.insertBefore(newNode, referenceNode.firstChild);
}
function insertSiblingAfter(referenceNode, newNode) {
referenceNode.parentNode.insertBefore(
newNode,
referenceNode.nextSibling
);
}
/* Show/hide all rows that are structural descendants of `parent`. */
function setChildRowVisibility(parent, flag) {
parent.visit(function (node) {
var tr = node.tr;
// currentFlag = node.hide ? false : flag; // fix for ext-filter
if (tr) {
tr.style.display = node.hide || !flag ? "none" : "";
}
if (!node.expanded) {
return "skip";
}
});
}
/* Find node that is rendered in previous row. */
function findPrevRowNode(node) {
var i,
last,
prev,
parent = node.parent,
siblings = parent ? parent.children : null;
if (siblings && siblings.length > 1 && siblings[0] !== node) {
// use the lowest descendant of the preceeding sibling
i = $.inArray(node, siblings);
prev = siblings[i - 1];
_assert(prev.tr);
// descend to lowest child (with a <tr> tag)
while (prev.children && prev.children.length) {
last = prev.children[prev.children.length - 1];
if (!last.tr) {
break;
}
prev = last;
}
} else {
// if there is no preceding sibling, use the direct parent
prev = parent;
}
return prev;
}
$.ui.fancytree.registerExtension({
name: "table",
version: "2.38.3",
// Default options for this extension.
options: {
checkboxColumnIdx: null, // render the checkboxes into the this column index (default: nodeColumnIdx)
indentation: 16, // indent every node level by 16px
mergeStatusColumns: true, // display 'nodata', 'loading', 'error' centered in a single, merged TR
nodeColumnIdx: 0, // render node expander, icon, and title to this column (default: #0)
},
// Overide virtual methods for this extension.
// `this` : is this extension object
// `this._super`: the virtual function that was overriden (member of prev. extension or Fancytree)
treeInit: function (ctx) {
var i,
n,
$row,
$tbody,
tree = ctx.tree,
opts = ctx.options,
tableOpts = opts.table,
$table = tree.widget.element;
if (tableOpts.customStatus != null) {
if (opts.renderStatusColumns == null) {
tree.warn(
"The 'customStatus' option is deprecated since v2.15.0. Use 'renderStatusColumns' instead."
);
opts.renderStatusColumns = tableOpts.customStatus;
} else {
$.error(
"The 'customStatus' option is deprecated since v2.15.0. Use 'renderStatusColumns' only instead."
);
}
}
if (opts.renderStatusColumns) {
if (opts.renderStatusColumns === true) {
opts.renderStatusColumns = opts.renderColumns;
// } else if( opts.renderStatusColumns === "wide" ) {
// opts.renderStatusColumns = _renderStatusNodeWide;
}
}
$table.addClass("fancytree-container fancytree-ext-table");
$tbody = $table.find(">tbody");
if (!$tbody.length) {
// TODO: not sure if we can rely on browsers to insert missing <tbody> before <tr>s:
if ($table.find(">tr").length) {
$.error(
"Expected table > tbody > tr. If you see this please open an issue."
);
}
$tbody = $("<tbody>").appendTo($table);
}
tree.tbody = $tbody[0];
// Prepare row templates:
// Determine column count from table header if any
tree.columnCount = $("thead >tr", $table)
.last()
.find(">th", $table).length;
// Read TR templates from tbody if any
$row = $tbody.children("tr").first();
if ($row.length) {
n = $row.children("td").length;
if (tree.columnCount && n !== tree.columnCount) {
tree.warn(
"Column count mismatch between thead (" +
tree.columnCount +
") and tbody (" +
n +
"): using tbody."
);
tree.columnCount = n;
}
$row = $row.clone();
} else {
// Only thead is defined: create default row markup
_assert(
tree.columnCount >= 1,
"Need either <thead> or <tbody> with <td> elements to determine column count."
);
$row = $("<tr />");
for (i = 0; i < tree.columnCount; i++) {
$row.append("<td />");
}
}
$row.find(">td")
.eq(tableOpts.nodeColumnIdx)
.html("<span class='fancytree-node' />");
if (opts.aria) {
$row.attr("role", "row");
$row.find("td").attr("role", "gridcell");
}
tree.rowFragment = document.createDocumentFragment();
tree.rowFragment.appendChild($row.get(0));
// // If tbody contains a second row, use this as status node template
// $row = $tbody.children("tr").eq(1);
// if( $row.length === 0 ) {
// tree.statusRowFragment = tree.rowFragment;
// } else {
// $row = $row.clone();
// tree.statusRowFragment = document.createDocumentFragment();
// tree.statusRowFragment.appendChild($row.get(0));
// }
//
$tbody.empty();
// Make sure that status classes are set on the node's <tr> elements
tree.statusClassPropName = "tr";
tree.ariaPropName = "tr";
this.nodeContainerAttrName = "tr";
// #489: make sure $container is set to <table>, even if ext-dnd is listed before ext-table
tree.$container = $table;
this._superApply(arguments);
// standard Fancytree created a root UL
$(tree.rootNode.ul).remove();
tree.rootNode.ul = null;
// Add container to the TAB chain
// #577: Allow to set tabindex to "0", "-1" and ""
this.$container.attr("tabindex", opts.tabindex);
// this.$container.attr("tabindex", opts.tabbable ? "0" : "-1");
if (opts.aria) {
tree.$container
.attr("role", "treegrid")
.attr("aria-readonly", true);
}
},
nodeRemoveChildMarkup: function (ctx) {
var node = ctx.node;
// node.debug("nodeRemoveChildMarkup()");
node.visit(function (n) {
if (n.tr) {
$(n.tr).remove();
n.tr = null;
}
});
},
nodeRemoveMarkup: function (ctx) {
var node = ctx.node;
// node.debug("nodeRemoveMarkup()");
if (node.tr) {
$(node.tr).remove();
node.tr = null;
}
this.nodeRemoveChildMarkup(ctx);
},
/* Override standard render. */
nodeRender: function (ctx, force, deep, collapsed, _recursive) {
var children,
firstTr,
i,
l,
newRow,
prevNode,
prevTr,
subCtx,
tree = ctx.tree,
node = ctx.node,
opts = ctx.options,
isRootNode = !node.parent;
if (tree._enableUpdate === false) {
// $.ui.fancytree.debug("*** nodeRender _enableUpdate: false");
return;
}
if (!_recursive) {
ctx.hasCollapsedParents = node.parent && !node.parent.expanded;
}
// $.ui.fancytree.debug("*** nodeRender " + node + ", isRoot=" + isRootNode, "tr=" + node.tr, "hcp=" + ctx.hasCollapsedParents, "parent.tr=" + (node.parent && node.parent.tr));
if (!isRootNode) {
if (node.tr && force) {
this.nodeRemoveMarkup(ctx);
}
if (node.tr) {
if (force) {
// Set icon, link, and title (normally this is only required on initial render)
this.nodeRenderTitle(ctx); // triggers renderColumns()
} else {
// Update element classes according to node state
this.nodeRenderStatus(ctx);
}
} else {
if (ctx.hasCollapsedParents && !deep) {
// #166: we assume that the parent will be (recursively) rendered
// later anyway.
// node.debug("nodeRender ignored due to unrendered parent");
return;
}
// Create new <tr> after previous row
// if( node.isStatusNode() ) {
// newRow = tree.statusRowFragment.firstChild.cloneNode(true);
// } else {
newRow = tree.rowFragment.firstChild.cloneNode(true);
// }
prevNode = findPrevRowNode(node);
// $.ui.fancytree.debug("*** nodeRender " + node + ": prev: " + prevNode.key);
_assert(prevNode);
if (collapsed === true && _recursive) {
// hide all child rows, so we can use an animation to show it later
newRow.style.display = "none";
} else if (deep && ctx.hasCollapsedParents) {
// also hide this row if deep === true but any parent is collapsed
newRow.style.display = "none";
// newRow.style.color = "red";
}
if (prevNode.tr) {
insertSiblingAfter(prevNode.tr, newRow);
} else {
_assert(
!prevNode.parent,
"prev. row must have a tr, or be system root"
);
// tree.tbody.appendChild(newRow);
insertFirstChild(tree.tbody, newRow); // #675
}
node.tr = newRow;
if (node.key && opts.generateIds) {
node.tr.id = opts.idPrefix + node.key;
}
node.tr.ftnode = node;
// if(opts.aria){
// $(node.tr).attr("aria-labelledby", "ftal_" + opts.idPrefix + node.key);
// }
node.span = $("span.fancytree-node", node.tr).get(0);
// Set icon, link, and title (normally this is only required on initial render)
this.nodeRenderTitle(ctx);
// Allow tweaking, binding, after node was created for the first time
// tree._triggerNodeEvent("createNode", ctx);
if (opts.createNode) {
opts.createNode.call(tree, { type: "createNode" }, ctx);
}
}
}
// Allow tweaking after node state was rendered
// tree._triggerNodeEvent("renderNode", ctx);
if (opts.renderNode) {
opts.renderNode.call(tree, { type: "renderNode" }, ctx);
}
// Visit child nodes
// Add child markup
children = node.children;
if (children && (isRootNode || deep || node.expanded)) {
for (i = 0, l = children.length; i < l; i++) {
subCtx = $.extend({}, ctx, { node: children[i] });
subCtx.hasCollapsedParents =
subCtx.hasCollapsedParents || !node.expanded;
this.nodeRender(subCtx, force, deep, collapsed, true);
}
}
// Make sure, that <tr> order matches node.children order.
if (children && !_recursive) {
// we only have to do it once, for the root branch
prevTr = node.tr || null;
firstTr = tree.tbody.firstChild;
// Iterate over all descendants
node.visit(function (n) {
if (n.tr) {
if (
!n.parent.expanded &&
n.tr.style.display !== "none"
) {
// fix after a node was dropped over a collapsed
n.tr.style.display = "none";
setChildRowVisibility(n, false);
}
if (n.tr.previousSibling !== prevTr) {
node.debug("_fixOrder: mismatch at node: " + n);
var nextTr = prevTr ? prevTr.nextSibling : firstTr;
tree.tbody.insertBefore(n.tr, nextTr);
}
prevTr = n.tr;
}
});
}
// Update element classes according to node state
// if(!isRootNode){
// this.nodeRenderStatus(ctx);
// }
},
nodeRenderTitle: function (ctx, title) {
var $cb,
res,
tree = ctx.tree,
node = ctx.node,
opts = ctx.options,
isStatusNode = node.isStatusNode();
res = this._super(ctx, title);
if (node.isRootNode()) {
return res;
}
// Move checkbox to custom column
if (
opts.checkbox &&
!isStatusNode &&
opts.table.checkboxColumnIdx != null
) {
$cb = $("span.fancytree-checkbox", node.span); //.detach();
$(node.tr)
.find("td")
.eq(+opts.table.checkboxColumnIdx)
.html($cb);
}
// Update element classes according to node state
this.nodeRenderStatus(ctx);
if (isStatusNode) {
if (opts.renderStatusColumns) {
// Let user code write column content
opts.renderStatusColumns.call(
tree,
{ type: "renderStatusColumns" },
ctx
);
} else if (opts.table.mergeStatusColumns && node.isTopLevel()) {
$(node.tr)
.find(">td")
.eq(0)
.prop("colspan", tree.columnCount)
.text(node.title)
.addClass("fancytree-status-merged")
.nextAll()
.remove();
} // else: default rendering for status node: leave other cells empty
} else if (opts.renderColumns) {
opts.renderColumns.call(tree, { type: "renderColumns" }, ctx);
}
return res;
},
nodeRenderStatus: function (ctx) {
var indent,
node = ctx.node,
opts = ctx.options;
this._super(ctx);
$(node.tr).removeClass("fancytree-node");
// indent
indent = (node.getLevel() - 1) * opts.table.indentation;
if (opts.rtl) {
$(node.span).css({ paddingRight: indent + "px" });
} else {
$(node.span).css({ paddingLeft: indent + "px" });
}
},
/* Expand node, return Deferred.promise. */
nodeSetExpanded: function (ctx, flag, callOpts) {
// flag defaults to true
flag = flag !== false;
if ((ctx.node.expanded && flag) || (!ctx.node.expanded && !flag)) {
// Expanded state isn't changed - just call base implementation
return this._superApply(arguments);
}
var dfd = new $.Deferred(),
subOpts = $.extend({}, callOpts, {
noEvents: true,
noAnimation: true,
});
callOpts = callOpts || {};
function _afterExpand(ok, args) {
// ctx.tree.info("ok:" + ok, args);
if (ok) {
// #1108 minExpandLevel: 2 together with table extension does not work
// don't call when 'ok' is false:
setChildRowVisibility(ctx.node, flag);
if (
flag &&
ctx.options.autoScroll &&
!callOpts.noAnimation &&
ctx.node.hasChildren()
) {
// Scroll down to last child, but keep current node visible
ctx.node
.getLastChild()
.scrollIntoView(true, { topNode: ctx.node })
.always(function () {
if (!callOpts.noEvents) {
ctx.tree._triggerNodeEvent(
flag ? "expand" : "collapse",
ctx
);
}
dfd.resolveWith(ctx.node);
});
} else {
if (!callOpts.noEvents) {
ctx.tree._triggerNodeEvent(
flag ? "expand" : "collapse",
ctx
);
}
dfd.resolveWith(ctx.node);
}
} else {
if (!callOpts.noEvents) {
ctx.tree._triggerNodeEvent(
flag ? "expand" : "collapse",
ctx
);
}
dfd.rejectWith(ctx.node);
}
}
// Call base-expand with disabled events and animation
this._super(ctx, flag, subOpts)
.done(function () {
_afterExpand(true, arguments);
})
.fail(function () {
_afterExpand(false, arguments);
});
return dfd.promise();
},
nodeSetStatus: function (ctx, status, message, details) {
if (status === "ok") {
var node = ctx.node,
firstChild = node.children ? node.children[0] : null;
if (firstChild && firstChild.isStatusNode()) {
$(firstChild.tr).remove();
}
}
return this._superApply(arguments);
},
treeClear: function (ctx) {
this.nodeRemoveChildMarkup(this._makeHookContext(this.rootNode));
return this._superApply(arguments);
},
treeDestroy: function (ctx) {
this.$container.find("tbody").empty();
if (this.$source) {
this.$source.removeClass("fancytree-helper-hidden");
}
return this._superApply(arguments);
},
/*,
treeSetFocus: function(ctx, flag) {
// alert("treeSetFocus" + ctx.tree.$container);
ctx.tree.$container.focus();
$.ui.fancytree.focusTree = ctx.tree;
}*/
});
// Value returned by `require('jquery.fancytree..')`
return $.ui.fancytree;
}); // End of closure

View file

@ -0,0 +1,125 @@
/*!
* jquery.fancytree.themeroller.js
*
* Enable jQuery UI ThemeRoller styles.
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
*
* @see http://jqueryui.com/themeroller/
*
* Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
*
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.38.3
* @date 2023-02-01T20:52:50Z
*/
(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";
/*******************************************************************************
* Extension code
*/
$.ui.fancytree.registerExtension({
name: "themeroller",
version: "2.38.3",
// Default options for this extension.
options: {
activeClass: "ui-state-active", // Class added to active node
// activeClass: "ui-state-highlight",
addClass: "ui-corner-all", // Class added to all nodes
focusClass: "ui-state-focus", // Class added to focused node
hoverClass: "ui-state-hover", // Class added to hovered node
selectedClass: "ui-state-highlight", // Class added to selected nodes
// selectedClass: "ui-state-active"
},
treeInit: function (ctx) {
var $el = ctx.widget.element,
opts = ctx.options.themeroller;
this._superApply(arguments);
if ($el[0].nodeName === "TABLE") {
$el.addClass("ui-widget ui-corner-all");
$el.find(">thead tr").addClass("ui-widget-header");
$el.find(">tbody").addClass("ui-widget-conent");
} else {
$el.addClass("ui-widget ui-widget-content ui-corner-all");
}
$el.on(
"mouseenter mouseleave",
".fancytree-node",
function (event) {
var node = $.ui.fancytree.getNode(event.target),
flag = event.type === "mouseenter";
$(node.tr ? node.tr : node.span).toggleClass(
opts.hoverClass + " " + opts.addClass,
flag
);
}
);
},
treeDestroy: function (ctx) {
this._superApply(arguments);
ctx.widget.element.removeClass(
"ui-widget ui-widget-content ui-corner-all"
);
},
nodeRenderStatus: function (ctx) {
var classes = {},
node = ctx.node,
$el = $(node.tr ? node.tr : node.span),
opts = ctx.options.themeroller;
this._super(ctx);
/*
.ui-state-highlight: Class to be applied to highlighted or selected elements. Applies "highlight" container styles to an element and its child text, links, and icons.
.ui-state-error: Class to be applied to error messaging container elements. Applies "error" container styles to an element and its child text, links, and icons.
.ui-state-error-text: An additional class that applies just the error text color without background. Can be used on form labels for instance. Also applies error icon color to child icons.
.ui-state-default: Class to be applied to clickable button-like elements. Applies "clickable default" container styles to an element and its child text, links, and icons.
.ui-state-hover: Class to be applied on mouseover to clickable button-like elements. Applies "clickable hover" container styles to an element and its child text, links, and icons.
.ui-state-focus: Class to be applied on keyboard focus to clickable button-like elements. Applies "clickable hover" container styles to an element and its child text, links, and icons.
.ui-state-active: Class to be applied on mousedown to clickable button-like elements. Applies "clickable active" container styles to an element and its child text, links, and icons.
*/
// Set ui-state-* class (handle the case that the same class is assigned
// to different states)
classes[opts.activeClass] = false;
classes[opts.focusClass] = false;
classes[opts.selectedClass] = false;
if (node.isActive()) {
classes[opts.activeClass] = true;
}
if (node.hasFocus()) {
classes[opts.focusClass] = true;
}
// activeClass takes precedence before selectedClass:
if (node.isSelected() && !node.isActive()) {
classes[opts.selectedClass] = true;
}
$el.toggleClass(opts.activeClass, classes[opts.activeClass]);
$el.toggleClass(opts.focusClass, classes[opts.focusClass]);
$el.toggleClass(opts.selectedClass, classes[opts.selectedClass]);
// Additional classes (e.g. 'ui-corner-all')
$el.addClass(opts.addClass);
},
});
// Value returned by `require('jquery.fancytree..')`
return $.ui.fancytree;
}); // End of closure

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,257 @@
/*!
* jquery.fancytree.wide.js
* Support for 100% wide selection bars.
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
*
* Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
*
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.38.3
* @date 2023-02-01T20:52:50Z
*/
(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";
var reNumUnit = /^([+-]?(?:\d+|\d*\.\d+))([a-z]*|%)$/; // split "1.5em" to ["1.5", "em"]
/*******************************************************************************
* Private functions and variables
*/
// var _assert = $.ui.fancytree.assert;
/* Calculate inner width without scrollbar */
// function realInnerWidth($el) {
// // http://blog.jquery.com/2012/08/16/jquery-1-8-box-sizing-width-csswidth-and-outerwidth/
// // inst.contWidth = parseFloat(this.$container.css("width"), 10);
// // 'Client width without scrollbar' - 'padding'
// return $el[0].clientWidth - ($el.innerWidth() - parseFloat($el.css("width"), 10));
// }
/* Create a global embedded CSS style for the tree. */
function defineHeadStyleElement(id, cssText) {
id = "fancytree-style-" + id;
var $headStyle = $("#" + id);
if (!cssText) {
$headStyle.remove();
return null;
}
if (!$headStyle.length) {
$headStyle = $("<style />")
.attr("id", id)
.addClass("fancytree-style")
.prop("type", "text/css")
.appendTo("head");
}
try {
$headStyle.html(cssText);
} catch (e) {
// fix for IE 6-8
$headStyle[0].styleSheet.cssText = cssText;
}
return $headStyle;
}
/* Calculate the CSS rules that indent title spans. */
function renderLevelCss(
containerId,
depth,
levelOfs,
lineOfs,
labelOfs,
measureUnit
) {
var i,
prefix = "#" + containerId + " span.fancytree-level-",
rules = [];
for (i = 0; i < depth; i++) {
rules.push(
prefix +
(i + 1) +
" span.fancytree-title { padding-left: " +
(i * levelOfs + lineOfs) +
measureUnit +
"; }"
);
}
// Some UI animations wrap the UL inside a DIV and set position:relative on both.
// This breaks the left:0 and padding-left:nn settings of the title
rules.push(
"#" +
containerId +
" div.ui-effects-wrapper ul li span.fancytree-title, " +
"#" +
containerId +
" li.fancytree-animating span.fancytree-title " + // #716
"{ padding-left: " +
labelOfs +
measureUnit +
"; position: static; width: auto; }"
);
return rules.join("\n");
}
// /**
// * [ext-wide] Recalculate the width of the selection bar after the tree container
// * was resized.<br>
// * May be called explicitly on container resize, since there is no resize event
// * for DIV tags.
// *
// * @alias Fancytree#wideUpdate
// * @requires jquery.fancytree.wide.js
// */
// $.ui.fancytree._FancytreeClass.prototype.wideUpdate = function(){
// var inst = this.ext.wide,
// prevCw = inst.contWidth,
// prevLo = inst.lineOfs;
// inst.contWidth = realInnerWidth(this.$container);
// // Each title is precceeded by 2 or 3 icons (16px + 3 margin)
// // + 1px title border and 3px title padding
// // TODO: use code from treeInit() below
// inst.lineOfs = (this.options.checkbox ? 3 : 2) * 19;
// if( prevCw !== inst.contWidth || prevLo !== inst.lineOfs ) {
// this.debug("wideUpdate: " + inst.contWidth);
// this.visit(function(node){
// node.tree._callHook("nodeRenderTitle", node);
// });
// }
// };
/*******************************************************************************
* Extension code
*/
$.ui.fancytree.registerExtension({
name: "wide",
version: "2.38.3",
// Default options for this extension.
options: {
iconWidth: null, // Adjust this if @fancy-icon-width != "16px"
iconSpacing: null, // Adjust this if @fancy-icon-spacing != "3px"
labelSpacing: null, // Adjust this if padding between icon and label != "3px"
levelOfs: null, // Adjust this if ul padding != "16px"
},
treeCreate: function (ctx) {
this._superApply(arguments);
this.$container.addClass("fancytree-ext-wide");
var containerId,
cssText,
iconSpacingUnit,
labelSpacingUnit,
iconWidthUnit,
levelOfsUnit,
instOpts = ctx.options.wide,
// css sniffing
$dummyLI = $(
"<li id='fancytreeTemp'><span class='fancytree-node'><span class='fancytree-icon' /><span class='fancytree-title' /></span><ul />"
).appendTo(ctx.tree.$container),
$dummyIcon = $dummyLI.find(".fancytree-icon"),
$dummyUL = $dummyLI.find("ul"),
// $dummyTitle = $dummyLI.find(".fancytree-title"),
iconSpacing =
instOpts.iconSpacing || $dummyIcon.css("margin-left"),
iconWidth = instOpts.iconWidth || $dummyIcon.css("width"),
labelSpacing = instOpts.labelSpacing || "3px",
levelOfs = instOpts.levelOfs || $dummyUL.css("padding-left");
$dummyLI.remove();
iconSpacingUnit = iconSpacing.match(reNumUnit)[2];
iconSpacing = parseFloat(iconSpacing, 10);
labelSpacingUnit = labelSpacing.match(reNumUnit)[2];
labelSpacing = parseFloat(labelSpacing, 10);
iconWidthUnit = iconWidth.match(reNumUnit)[2];
iconWidth = parseFloat(iconWidth, 10);
levelOfsUnit = levelOfs.match(reNumUnit)[2];
if (
iconSpacingUnit !== iconWidthUnit ||
levelOfsUnit !== iconWidthUnit ||
labelSpacingUnit !== iconWidthUnit
) {
$.error(
"iconWidth, iconSpacing, and levelOfs must have the same css measure unit"
);
}
this._local.measureUnit = iconWidthUnit;
this._local.levelOfs = parseFloat(levelOfs);
this._local.lineOfs =
(1 +
(ctx.options.checkbox ? 1 : 0) +
(ctx.options.icon === false ? 0 : 1)) *
(iconWidth + iconSpacing) +
iconSpacing;
this._local.labelOfs = labelSpacing;
this._local.maxDepth = 10;
// Get/Set a unique Id on the container (if not already exists)
containerId = this.$container.uniqueId().attr("id");
// Generated css rules for some levels (extended on demand)
cssText = renderLevelCss(
containerId,
this._local.maxDepth,
this._local.levelOfs,
this._local.lineOfs,
this._local.labelOfs,
this._local.measureUnit
);
defineHeadStyleElement(containerId, cssText);
},
treeDestroy: function (ctx) {
// Remove generated css rules
defineHeadStyleElement(this.$container.attr("id"), null);
return this._superApply(arguments);
},
nodeRenderStatus: function (ctx) {
var containerId,
cssText,
res,
node = ctx.node,
level = node.getLevel();
res = this._super(ctx);
// Generate some more level-n rules if required
if (level > this._local.maxDepth) {
containerId = this.$container.attr("id");
this._local.maxDepth *= 2;
node.debug(
"Define global ext-wide css up to level " +
this._local.maxDepth
);
cssText = renderLevelCss(
containerId,
this._local.maxDepth,
this._local.levelOfs,
this._local.lineOfs,
this._local.labelSpacing,
this._local.measureUnit
);
defineHeadStyleElement(containerId, cssText);
}
// Add level-n class to apply indentation padding.
// (Setting element style would not work, since it cannot easily be
// overriden while animations run)
$(node.span).addClass("fancytree-level-" + level);
return res;
},
});
// Value returned by `require('jquery.fancytree..')`
return $.ui.fancytree;
}); // End of closure