/*! jQuery Fancytree Plugin - 2.31.0 - 2019-05-31T11:32:38Z * https://github.com/mar10/fancytree * Copyright (c) 2019 Martin Wendt; Licensed MIT */ /*! jQuery UI - v1.12.1 - 2018-05-20 * http://jqueryui.com * Includes: widget.js, position.js, keycode.js, scroll-parent.js, unique-id.js * Copyright jQuery Foundation and other contributors; Licensed MIT */ /* NOTE: Original jQuery UI wrapper was replaced with a simple IIFE. See README-Fancytree.md */ (function( $ ) { $.ui = $.ui || {}; var version = $.ui.version = "1.12.1"; /*! * jQuery UI Widget 1.12.1 * http://jqueryui.com * * Copyright jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license */ //>>label: Widget //>>group: Core //>>description: Provides a factory for creating stateful widgets with a common API. //>>docs: http://api.jqueryui.com/jQuery.widget/ //>>demos: http://jqueryui.com/widget/ var widgetUuid = 0; var widgetSlice = Array.prototype.slice; $.cleanData = ( function( orig ) { return function( elems ) { var events, elem, i; for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) { try { // Only trigger remove when necessary to save time events = $._data( elem, "events" ); if ( events && events.remove ) { $( elem ).triggerHandler( "remove" ); } // Http://bugs.jquery.com/ticket/8235 } catch ( e ) {} } orig( elems ); }; } )( $.cleanData ); $.widget = function( name, base, prototype ) { var existingConstructor, constructor, basePrototype; // ProxiedPrototype allows the provided prototype to remain unmodified // so that it can be used as a mixin for multiple widgets (#8876) var proxiedPrototype = {}; var namespace = name.split( "." )[ 0 ]; name = name.split( "." )[ 1 ]; var fullName = namespace + "-" + name; if ( !prototype ) { prototype = base; base = $.Widget; } if ( $.isArray( prototype ) ) { prototype = $.extend.apply( null, [ {} ].concat( prototype ) ); } // Create selector for plugin $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { return !!$.data( elem, fullName ); }; $[ namespace ] = $[ namespace ] || {}; existingConstructor = $[ namespace ][ name ]; constructor = $[ namespace ][ name ] = function( options, element ) { // Allow instantiation without "new" keyword if ( !this._createWidget ) { return new constructor( options, element ); } // Allow instantiation without initializing for simple inheritance // must use "new" keyword (the code above always passes args) if ( arguments.length ) { this._createWidget( options, element ); } }; // Extend with the existing constructor to carry over any static properties $.extend( constructor, existingConstructor, { version: prototype.version, // Copy the object used to create the prototype in case we need to // redefine the widget later _proto: $.extend( {}, prototype ), // Track widgets that inherit from this widget in case this widget is // redefined after a widget inherits from it _childConstructors: [] } ); basePrototype = new base(); // We need to make the options hash a property directly on the new instance // otherwise we'll modify the options hash on the prototype that we're // inheriting from basePrototype.options = $.widget.extend( {}, basePrototype.options ); $.each( prototype, function( prop, value ) { if ( !$.isFunction( value ) ) { proxiedPrototype[ prop ] = value; return; } proxiedPrototype[ prop ] = ( function() { function _super() { return base.prototype[ prop ].apply( this, arguments ); } function _superApply( args ) { return base.prototype[ prop ].apply( this, args ); } return function() { var __super = this._super; var __superApply = this._superApply; var returnValue; this._super = _super; this._superApply = _superApply; returnValue = value.apply( this, arguments ); this._super = __super; this._superApply = __superApply; return returnValue; }; } )(); } ); constructor.prototype = $.widget.extend( basePrototype, { // TODO: remove support for widgetEventPrefix // always use the name + a colon as the prefix, e.g., draggable:start // don't prefix for widgets that aren't DOM-based widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name }, proxiedPrototype, { constructor: constructor, namespace: namespace, widgetName: name, widgetFullName: fullName } ); // If this widget is being redefined then we need to find all widgets that // are inheriting from it and redefine all of them so that they inherit from // the new version of this widget. We're essentially trying to replace one // level in the prototype chain. if ( existingConstructor ) { $.each( existingConstructor._childConstructors, function( i, child ) { var childPrototype = child.prototype; // Redefine the child widget using the same prototype that was // originally used, but inherit from the new version of the base $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); } ); // Remove the list of existing child constructors from the old constructor // so the old child constructors can be garbage collected delete existingConstructor._childConstructors; } else { base._childConstructors.push( constructor ); } $.widget.bridge( name, constructor ); return constructor; }; $.widget.extend = function( target ) { var input = widgetSlice.call( arguments, 1 ); var inputIndex = 0; var inputLength = input.length; var key; var value; for ( ; inputIndex < inputLength; inputIndex++ ) { for ( key in input[ inputIndex ] ) { value = input[ inputIndex ][ key ]; if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { // Clone objects if ( $.isPlainObject( value ) ) { target[ key ] = $.isPlainObject( target[ key ] ) ? $.widget.extend( {}, target[ key ], value ) : // Don't extend strings, arrays, etc. with objects $.widget.extend( {}, value ); // Copy everything else by reference } else { target[ key ] = value; } } } } return target; }; $.widget.bridge = function( name, object ) { var fullName = object.prototype.widgetFullName || name; $.fn[ name ] = function( options ) { var isMethodCall = typeof options === "string"; var args = widgetSlice.call( arguments, 1 ); var returnValue = this; if ( isMethodCall ) { // If this is an empty collection, we need to have the instance method // return undefined instead of the jQuery instance if ( !this.length && options === "instance" ) { returnValue = undefined; } else { this.each( function() { var methodValue; var instance = $.data( this, fullName ); if ( options === "instance" ) { returnValue = instance; return false; } if ( !instance ) { return $.error( "cannot call methods on " + name + " prior to initialization; " + "attempted to call method '" + options + "'" ); } if ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === "_" ) { return $.error( "no such method '" + options + "' for " + name + " widget instance" ); } methodValue = instance[ options ].apply( instance, args ); if ( methodValue !== instance && methodValue !== undefined ) { returnValue = methodValue && methodValue.jquery ? returnValue.pushStack( methodValue.get() ) : methodValue; return false; } } ); } } else { // Allow multiple hashes to be passed on init if ( args.length ) { options = $.widget.extend.apply( null, [ options ].concat( args ) ); } this.each( function() { var instance = $.data( this, fullName ); if ( instance ) { instance.option( options || {} ); if ( instance._init ) { instance._init(); } } else { $.data( this, fullName, new object( options, this ) ); } } ); } return returnValue; }; }; $.Widget = function( /* options, element */ ) {}; $.Widget._childConstructors = []; $.Widget.prototype = { widgetName: "widget", widgetEventPrefix: "", defaultElement: "
* 'child': append this node as last child of targetNode. * This is the default. To be compatble with the D'n'd * hitMode, we also accept 'over'. * 'firstChild': add this node as first child of targetNode. * 'before': add this node as sibling before targetNode. * 'after': add this node as sibling after targetNode.* @param {function} [map] optional callback(FancytreeNode) to allow modifcations */ moveTo: function(targetNode, mode, map) { if (mode === undefined || mode === "over") { mode = "child"; } else if (mode === "firstChild") { if (targetNode.children && targetNode.children.length) { mode = "before"; targetNode = targetNode.children[0]; } else { mode = "child"; } } var pos, tree = this.tree, prevParent = this.parent, targetParent = mode === "child" ? targetNode : targetNode.parent; if (this === targetNode) { return; } else if (!this.parent) { $.error("Cannot move system root"); } else if (targetParent.isDescendantOf(this)) { $.error("Cannot move a node to its own descendant"); } if (targetParent !== prevParent) { prevParent.triggerModifyChild("remove", this); } // Unlink this node from current parent if (this.parent.children.length === 1) { if (this.parent === targetParent) { return; // #258 } this.parent.children = this.parent.lazy ? [] : null; this.parent.expanded = false; } else { pos = $.inArray(this, this.parent.children); _assert(pos >= 0, "invalid source parent"); this.parent.children.splice(pos, 1); } // Remove from source DOM parent // if(this.parent.ul){ // this.parent.ul.removeChild(this.li); // } // Insert this node to target parent's child list this.parent = targetParent; if (targetParent.hasChildren()) { switch (mode) { case "child": // Append to existing target children targetParent.children.push(this); break; case "before": // Insert this node before target node pos = $.inArray(targetNode, targetParent.children); _assert(pos >= 0, "invalid target parent"); targetParent.children.splice(pos, 0, this); break; case "after": // Insert this node after target node pos = $.inArray(targetNode, targetParent.children); _assert(pos >= 0, "invalid target parent"); targetParent.children.splice(pos + 1, 0, this); break; default: $.error("Invalid mode " + mode); } } else { targetParent.children = [this]; } // Parent has no
// Access widget methods and members:
* var tree = $("#tree").fancytree("getTree");
* var node = $("#tree").fancytree("getActiveNode", "1234");
*
*
* @mixin Fancytree_Widget
*/
$.widget(
"ui.fancytree",
/** @lends Fancytree_Widget# */
{
/**These options will be used as defaults
* @type {FancytreeOptions}
*/
options: {
activeVisible: true,
ajax: {
type: "GET",
cache: false, // false: Append random '_' argument to the request url to prevent caching.
// timeout: 0, // >0: Make sure we get an ajax error if server is unreachable
dataType: "json", // Expect json format and pass json object to callbacks.
},
aria: true,
autoActivate: true,
autoCollapse: false,
autoScroll: false,
checkbox: false,
clickFolderMode: 4,
debugLevel: null, // 0..4 (null: use global setting $.ui.fancytree.debugLevel)
disabled: false, // TODO: required anymore?
enableAspx: true,
escapeTitles: false,
extensions: [],
// fx: { height: "toggle", duration: 200 },
// toggleEffect: { effect: "drop", options: {direction: "left"}, duration: 200 },
// toggleEffect: { effect: "slide", options: {direction: "up"}, duration: 200 },
//toggleEffect: { effect: "blind", options: {direction: "vertical", scale: "box"}, duration: 200 },
toggleEffect: { effect: "slideToggle", duration: 200 }, //< "toggle" or "slideToggle" to use jQuery instead of jQueryUI for toggleEffect animation
generateIds: false,
icon: true,
idPrefix: "ft_",
focusOnSelect: false,
keyboard: true,
keyPathSeparator: "/",
minExpandLevel: 1,
nodata: true, // (bool, string, or callback) display message, when no data available
quicksearch: false,
rtl: false,
scrollOfs: { top: 0, bottom: 0 },
scrollParent: null,
selectMode: 2,
strings: {
loading: "Loading...", // … would be escaped when escapeTitles is true
loadError: "Load error!",
moreData: "More...",
noData: "No data.",
},
tabindex: "0",
titlesTabbable: false,
tooltip: false,
treeId: null,
_classNames: {
node: "fancytree-node",
folder: "fancytree-folder",
animating: "fancytree-animating",
combinedExpanderPrefix: "fancytree-exp-",
combinedIconPrefix: "fancytree-ico-",
hasChildren: "fancytree-has-children",
active: "fancytree-active",
selected: "fancytree-selected",
expanded: "fancytree-expanded",
lazy: "fancytree-lazy",
focused: "fancytree-focused",
partload: "fancytree-partload",
partsel: "fancytree-partsel",
radio: "fancytree-radio",
// radiogroup: "fancytree-radiogroup",
unselectable: "fancytree-unselectable",
lastsib: "fancytree-lastsib",
loading: "fancytree-loading",
error: "fancytree-error",
statusNodePrefix: "fancytree-statusnode-",
},
// events
lazyLoad: null,
postProcess: null,
},
/* Set up the widget, Called on first $().fancytree() */
_create: function() {
this.tree = new Fancytree(this);
this.$source =
this.source || this.element.data("type") === "json"
? this.element
: this.element.find(">ul").first();
// Subclass Fancytree instance with all enabled extensions
var extension,
extName,
i,
opts = this.options,
extensions = opts.extensions,
base = this.tree;
for (i = 0; i < extensions.length; i++) {
extName = extensions[i];
extension = $.ui.fancytree._extensions[extName];
if (!extension) {
$.error(
"Could not apply extension '" +
extName +
"' (it is not registered, did you forget to include it?)"
);
}
// Add extension options as tree.options.EXTENSION
// _assert(!this.tree.options[extName], "Extension name must not exist as option name: " + extName);
// console.info("extend " + extName, extension.options, this.tree.options[extName])
// issue #876: we want to replace custom array-options, not merge them
this.tree.options[extName] = _simpleDeepMerge(
{},
extension.options,
this.tree.options[extName]
);
// this.tree.options[extName] = $.extend(true, {}, extension.options, this.tree.options[extName]);
// console.info("extend " + extName + " =>", this.tree.options[extName])
// console.info("extend " + extName + " org default =>", extension.options)
// Add a namespace tree.ext.EXTENSION, to hold instance data
_assert(
this.tree.ext[extName] === undefined,
"Extension name must not exist as Fancytree.ext attribute: '" +
extName +
"'"
);
// this.tree[extName] = extension;
this.tree.ext[extName] = {};
// Subclass Fancytree methods using proxies.
_subclassObject(this.tree, base, extension, extName);
// current extension becomes base for the next extension
base = extension;
}
//
if (opts.icons !== undefined) {
// 2015-11-16
if (opts.icon === true) {
this.tree.warn(
"'icons' tree option is deprecated since v2.14.0: use 'icon' instead"
);
opts.icon = opts.icons;
} else {
$.error(
"'icons' tree option is deprecated since v2.14.0: use 'icon' only instead"
);
}
}
if (opts.iconClass !== undefined) {
// 2015-11-16
if (opts.icon) {
$.error(
"'iconClass' tree option is deprecated since v2.14.0: use 'icon' only instead"
);
} else {
this.tree.warn(
"'iconClass' tree option is deprecated since v2.14.0: use 'icon' instead"
);
opts.icon = opts.iconClass;
}
}
if (opts.tabbable !== undefined) {
// 2016-04-04
opts.tabindex = opts.tabbable ? "0" : "-1";
this.tree.warn(
"'tabbable' tree option is deprecated since v2.17.0: use 'tabindex='" +
opts.tabindex +
"' instead"
);
}
//
this.tree._callHook("treeCreate", this.tree);
// Note: 'fancytreecreate' event is fired by widget base class
// this.tree._triggerTreeEvent("create");
},
/* Called on every $().fancytree() */
_init: function() {
this.tree._callHook("treeInit", this.tree);
// TODO: currently we call bind after treeInit, because treeInit
// might change tree.$container.
// It would be better, to move event binding into hooks altogether
this._bind();
},
/* Use the _setOption method to respond to changes to options. */
_setOption: function(key, value) {
return this.tree._callHook(
"treeSetOption",
this.tree,
key,
value
);
},
/** Use the destroy method to clean up any modifications your widget has made to the DOM */
destroy: function() {
this._unbind();
this.tree._callHook("treeDestroy", this.tree);
// In jQuery UI 1.8, you must invoke the destroy method from the base widget
$.Widget.prototype.destroy.call(this);
// TODO: delete tree and nodes to make garbage collect easier?
// TODO: In jQuery UI 1.9 and above, you would define _destroy instead of destroy and not call the base method
},
// -------------------------------------------------------------------------
/* Remove all event handlers for our namespace */
_unbind: function() {
var ns = this.tree._ns;
this.element.off(ns);
this.tree.$container.off(ns);
$(document).off(ns);
},
/* Add mouse and kyboard handlers to the container */
_bind: function() {
var self = this,
opts = this.options,
tree = this.tree,
ns = tree._ns;
// selstartEvent = ( $.support.selectstart ? "selectstart" : "mousedown" )
// Remove all previuous handlers for this tree
this._unbind();
//alert("keydown" + ns + "foc=" + tree.hasFocus() + tree.$container);
// tree.debug("bind events; container: ", tree.$container);
tree.$container
.on("focusin" + ns + " focusout" + ns, function(event) {
var node = FT.getNode(event),
flag = event.type === "focusin";
if (!flag && node && $(event.target).is("a")) {
// #764
node.debug(
"Ignored focusout on embedded element."
);
return;
}
// tree.treeOnFocusInOut.call(tree, event);
// tree.debug("Tree container got event " + event.type, node, event, FT.getEventTarget(event));
if (flag) {
if (tree._getExpiringValue("focusin")) {
// #789: IE 11 may send duplicate focusin events
tree.debug("Ignored double focusin.");
return;
}
tree._setExpiringValue("focusin", true, 50);
if (!node) {
// #789: IE 11 may send focusin before mousdown(?)
node = tree._getExpiringValue("mouseDownNode");
if (node) {
tree.debug(
"Reconstruct mouse target for focusin from recent event."
);
}
}
}
if (node) {
// For example clicking into an that is part of a node
tree._callHook(
"nodeSetFocus",
tree._makeHookContext(node, event),
flag
);
} else {
if (
tree.tbody &&
$(event.target).parents(
"table.fancytree-container > thead"
).length
) {
// #767: ignore events in the table's header
tree.debug(
"Ignore focus event outside table body.",
event
);
} else {
tree._callHook("treeSetFocus", tree, flag);
}
}
})
.on("selectstart" + ns, "span.fancytree-title", function(
event
) {
// prevent mouse-drags to select text ranges
// tree.debug(" got event " + event.type);
event.preventDefault();
})
.on("keydown" + ns, function(event) {
// TODO: also bind keyup and keypress
// tree.debug("got event " + event.type + ", hasFocus:" + tree.hasFocus());
// if(opts.disabled || opts.keyboard === false || !tree.hasFocus() ){
if (opts.disabled || opts.keyboard === false) {
return true;
}
var res,
node = tree.focusNode, // node may be null
ctx = tree._makeHookContext(node || tree, event),
prevPhase = tree.phase;
try {
tree.phase = "userEvent";
// If a 'fancytreekeydown' handler returns false, skip the default
// handling (implemented by tree.nodeKeydown()).
if (node) {
res = tree._triggerNodeEvent(
"keydown",
node,
event
);
} else {
res = tree._triggerTreeEvent("keydown", event);
}
if (res === "preventNav") {
res = true; // prevent keyboard navigation, but don't prevent default handling of embedded input controls
} else if (res !== false) {
res = tree._callHook("nodeKeydown", ctx);
}
return res;
} finally {
tree.phase = prevPhase;
}
})
.on("mousedown" + ns, function(event) {
var et = FT.getEventTarget(event);
// self.tree.debug("event(" + event.type + "): node: ", et.node);
// #712: Store the clicked node, so we can use it when we get a focusin event
// ('click' event fires after focusin)
// tree.debug("event(" + event.type + "): node: ", et.node);
tree._lastMousedownNode = et ? et.node : null;
// #789: Store the node also for a short period, so we can use it
// in a *resulting* focusin event
tree._setExpiringValue(
"mouseDownNode",
tree._lastMousedownNode
);
})
.on("click" + ns + " dblclick" + ns, function(event) {
if (opts.disabled) {
return true;
}
var ctx,
et = FT.getEventTarget(event),
node = et.node,
tree = self.tree,
prevPhase = tree.phase;
// self.tree.debug("event(" + event.type + "): node: ", node);
if (!node) {
return true; // Allow bubbling of other events
}
ctx = tree._makeHookContext(node, event);
// self.tree.debug("event(" + event.type + "): node: ", node);
try {
tree.phase = "userEvent";
switch (event.type) {
case "click":
ctx.targetType = et.type;
if (node.isPagingNode()) {
return (
tree._triggerNodeEvent(
"clickPaging",
ctx,
event
) === true
);
}
return tree._triggerNodeEvent(
"click",
ctx,
event
) === false
? false
: tree._callHook("nodeClick", ctx);
case "dblclick":
ctx.targetType = et.type;
return tree._triggerNodeEvent(
"dblclick",
ctx,
event
) === false
? false
: tree._callHook("nodeDblclick", ctx);
}
} finally {
tree.phase = prevPhase;
}
});
},
/** Return the active node or null.
* @returns {FancytreeNode}
*/
getActiveNode: function() {
return this.tree.activeNode;
},
/** Return the matching node or null.
* @param {string} key
* @returns {FancytreeNode}
*/
getNodeByKey: function(key) {
return this.tree.getNodeByKey(key);
},
/** Return the invisible system root node.
* @returns {FancytreeNode}
*/
getRootNode: function() {
return this.tree.rootNode;
},
/** Return the current tree instance.
* @returns {Fancytree}
*/
getTree: function() {
return this.tree;
},
}
);
// $.ui.fancytree was created by the widget factory. Create a local shortcut:
FT = $.ui.fancytree;
/**
* Static members in the `$.ui.fancytree` namespace.// Access static members: * var node = $.ui.fancytree.getNode(element); * alert($.ui.fancytree.version); ** * @mixin Fancytree_Static */ $.extend( $.ui.fancytree, /** @lends Fancytree_Static# */ { /** @type {string} */ version: "2.31.0", // Set to semver by 'grunt release' /** @type {string} */ buildType: "production", // Set to 'production' by 'grunt build' /** @type {int} */ debugLevel: 3, // Set to 3 by 'grunt build' // Used by $.ui.fancytree.debug() and as default for tree.options.debugLevel _nextId: 1, _nextNodeKey: 1, _extensions: {}, // focusTree: null, /** Expose class object as $.ui.fancytree._FancytreeClass */ _FancytreeClass: Fancytree, /** Expose class object as $.ui.fancytree._FancytreeNodeClass */ _FancytreeNodeClass: FancytreeNode, /* Feature checks to provide backwards compatibility */ jquerySupports: { // http://jqueryui.com/upgrade-guide/1.9/#deprecated-offset-option-merged-into-my-and-at positionMyOfs: isVersionAtLeast($.ui.version, 1, 9), }, /** Throw an error if condition fails (debug method). * @param {boolean} cond * @param {string} msg */ assert: function(cond, msg) { return _assert(cond, msg); }, /** Create a new Fancytree instance on a target element. * * @param {Element | jQueryObject | string} el Target DOM element or selector * @param {FancytreeOptions} [opts] Fancytree options * @returns {Fancytree} new tree instance * @example * var tree = $.ui.fancytree.createTree("#tree", { * source: {url: "my/webservice"} * }); // Create tree for this matching element * * @since 2.25 */ createTree: function(el, opts) { var tree = $(el) .fancytree(opts) .fancytree("getTree"); return tree; }, /** Return a function that executes *fn* at most every *timeout* ms. * @param {integer} timeout * @param {function} fn * @param {boolean} [invokeAsap=false] * @param {any} [ctx] */ debounce: function(timeout, fn, invokeAsap, ctx) { var timer; if (arguments.length === 3 && typeof invokeAsap !== "boolean") { ctx = invokeAsap; invokeAsap = false; } return function() { var args = arguments; ctx = ctx || this; // eslint-disable-next-line no-unused-expressions invokeAsap && !timer && fn.apply(ctx, args); clearTimeout(timer); timer = setTimeout(function() { // eslint-disable-next-line no-unused-expressions invokeAsap || fn.apply(ctx, args); timer = null; }, timeout); }; }, /** Write message to console if debugLevel >= 4 * @param {string} msg */ debug: function(msg) { if ($.ui.fancytree.debugLevel >= 4) { consoleApply("log", arguments); } }, /** Write error message to console if debugLevel >= 1. * @param {string} msg */ error: function(msg) { if ($.ui.fancytree.debugLevel >= 1) { consoleApply("error", arguments); } }, /** Convert <, >, &, ", ', / to the equivalent entities. * * @param {string} s * @returns {string} */ escapeHtml: function(s) { return ("" + s).replace(REX_HTML, function(s) { return ENTITY_MAP[s]; }); }, /** Make jQuery.position() arguments backwards compatible, i.e. if * jQuery UI version <= 1.8, convert * { my: "left+3 center", at: "left bottom", of: $target } * to * { my: "left center", at: "left bottom", of: $target, offset: "3 0" } * * See http://jqueryui.com/upgrade-guide/1.9/#deprecated-offset-option-merged-into-my-and-at * and http://jsfiddle.net/mar10/6xtu9a4e/ * * @param {object} opts * @returns {object} the (potentially modified) original opts hash object */ fixPositionOptions: function(opts) { if (opts.offset || ("" + opts.my + opts.at).indexOf("%") >= 0) { $.error( "expected new position syntax (but '%' is not supported)" ); } if (!$.ui.fancytree.jquerySupports.positionMyOfs) { var // parse 'left+3 center' into ['left+3 center', 'left', '+3', 'center', undefined] myParts = /(\w+)([+-]?\d+)?\s+(\w+)([+-]?\d+)?/.exec( opts.my ), atParts = /(\w+)([+-]?\d+)?\s+(\w+)([+-]?\d+)?/.exec( opts.at ), // convert to numbers dx = (myParts[2] ? +myParts[2] : 0) + (atParts[2] ? +atParts[2] : 0), dy = (myParts[4] ? +myParts[4] : 0) + (atParts[4] ? +atParts[4] : 0); opts = $.extend({}, opts, { // make a copy and overwrite my: myParts[1] + " " + myParts[3], at: atParts[1] + " " + atParts[3], }); if (dx || dy) { opts.offset = "" + dx + " " + dy; } } return opts; }, /** Return a {node: FancytreeNode, type: TYPE} object for a mouse event. * * @param {Event} event Mouse event, e.g. click, ... * @returns {object} Return a {node: FancytreeNode, type: TYPE} object * TYPE: 'title' | 'prefix' | 'expander' | 'checkbox' | 'icon' | undefined */ getEventTarget: function(event) { var $target, tree, tcn = event && event.target ? event.target.className : "", res = { node: this.getNode(event.target), type: undefined }; // We use a fast version of $(res.node).hasClass() // See http://jsperf.com/test-for-classname/2 if (/\bfancytree-title\b/.test(tcn)) { res.type = "title"; } else if (/\bfancytree-expander\b/.test(tcn)) { res.type = res.node.hasChildren() === false ? "prefix" : "expander"; // }else if( /\bfancytree-checkbox\b/.test(tcn) || /\bfancytree-radio\b/.test(tcn) ){ } else if (/\bfancytree-checkbox\b/.test(tcn)) { res.type = "checkbox"; } else if (/\bfancytree(-custom)?-icon\b/.test(tcn)) { res.type = "icon"; } else if (/\bfancytree-node\b/.test(tcn)) { // Somewhere near the title res.type = "title"; } else if (event && event.target) { $target = $(event.target); if ($target.is("ul[role=group]")) { // #nnn: Clicking right to a node may hit the surrounding UL tree = res.node && res.node.tree; (tree || FT).debug("Ignoring click on outer UL."); res.node = null; } else if ($target.closest(".fancytree-title").length) { // #228: clicking an embedded element inside a title res.type = "title"; } else if ($target.closest(".fancytree-checkbox").length) { // E.g.