+ * '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, + 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.debugInfo) + 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, + _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
// 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.30.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; + invokeAsap && !timer && fn.apply(ctx, args); + clearTimeout(timer); + timer = setTimeout(function() { + invokeAsap || fn.apply(ctx, args); + timer = null; + }, timeout); + }; + }, + /** Write message to console if debugLevel >= 4 + * @param {string} msg + */ + debug: function(msg){ + /*jshint expr:true */ + ($.ui.fancytree.debugLevel >= 4) && consoleApply("log", arguments); + }, + /** Write error message to console if debugLevel >= 1. + * @param {string} msg + */ + error: function(msg){ + ($.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.