vendor/assets/javascripts/fancytree/jquery.fancytree.js in fancytree-rails-0.0.2 vs vendor/assets/javascripts/fancytree/jquery.fancytree.js in fancytree-rails-2.0.0.pre.6.pre.1
- old
+ new
@@ -1,18 +1,22 @@
/*!
* jquery.fancytree.js
* Dynamic tree view control, with support for lazy loading of branches.
* https://github.com/mar10/fancytree/
*
- * Copyright (c) 2006-2013, Martin Wendt (http://wwWendt.de)
+ * Copyright (c) 2006-2014, Martin Wendt (http://wwWendt.de)
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
- * @version 2.0.0-5
- * @date 2014-01-04T16:42
+ * @version 2.0.0-6
+ * @date 2014-02-10T10:52
*/
+/** Core Fancytree module.
+ */
+
+
// Start of local namespace
;(function($, window, document, undefined) {
"use strict";
// prevent duplicate loading
@@ -172,11 +176,11 @@
};
}
var i,
FT = null, // initialized below
- //Boolean attributes that can be set with equivalent class names in the LI tags
+ //boolean attributes that can be set with equivalent class names in the LI tags
CLASS_ATTRS = "active expanded focus folder lazy selected unselectable".split(" "),
CLASS_ATTR_MAP = {},
// Top-level Fancytree node attributes, that can be set by dict
NODE_ATTRS = "expanded extraClasses folder hideCheckbox key lazy selected title tooltip unselectable".split(" "),
NODE_ATTR_MAP = {},
@@ -191,33 +195,33 @@
* FancytreeNode
*/
/**
- * Creates a new node
- * @class Represents the hierarchical data model and operations.
- * @name FancytreeNode
- * @constructor
+ * Creates a new FancytreeNode
+ *
+ * @class FancytreeNode
+ * @classdesc A FancytreeNode represents the hierarchical data model and operations.
+ *
* @param {FancytreeNode} parent
- * @param {NodeData} data
+ * @param {NodeData} obj
*
* @property {Fancytree} tree
* @property {FancytreeNode} parent Parent node
- * @property {String} key
- * @property {String} title
+ * @property {string} key
+ * @property {string} title
* @property {object} data Contains all extra data that was passed on node creation
* @property {FancytreeNode[] | null | undefined} children list of child nodes
- * @property {Boolean} isStatusNode
- * @property {Boolean} expanded
- * @property {Boolean} folder
- * @property {Boolean} href
- * @property {String} extraClasses
- * @property {Boolean} lazy
- * @property {Boolean} nolink OBSOLETE
- * @property {Boolean} selected
- * @property {String} target
- * @property {String} tooltip
+ * @property {boolean} isStatusNode
+ * @property {boolean} expanded
+ * @property {boolean} folder
+ * @property {string} extraClasses
+ * @property {boolean} lazy
+ * @property {boolean} selected
+ * @property {string} tooltip
+ * @property {string} data.href
+ * @property {string} data.target
*/
function FancytreeNode(parent, obj){
var i, l, name, cl;
this.parent = parent;
@@ -264,11 +268,11 @@
this._setChildren(cl);
}
}
-FancytreeNode.prototype = /**@lends FancytreeNode*/{
+FancytreeNode.prototype = /** @lends FancytreeNode# */{
/* Return the direct child FancytreeNode with a given key, index. */
_findDirectChild: function(ptr){
var i, l,
cl = this.children;
@@ -299,20 +303,15 @@
},
/**
* Append (or insert) a list of child nodes.
*
* @param {NodeData[]} children array of child node definitions (also single child accepted)
- * @param {FancytreeNode | String | Integer} [insertBefore] child node (or key or index of such).
+ * @param {FancytreeNode | string | Integer} [insertBefore] child node (or key or index of such).
* If omitted, the new children are appended.
* @returns {FancytreeNode} first child added
*
- * @see applyPatch to modify existing child nodes.
- * @see FanctreeNode.applyPatch to modify existing child nodes.
- * @see FanctreeNode#applyPatch to modify existing child nodes.
- * @see applyPatch
- * @see FanctreeNode.applyPatch
- * @see FanctreeNode#applyPatch
+ * @see FancytreeNode#applyPatch
*/
addChildren: function(children, insertBefore){
var i, l, pos,
firstNode = null,
nodeList = [];
@@ -346,12 +345,14 @@
return firstNode;
},
/**
* Append or prepend a node, or append a child node.
*
+ * This a convenience function that calls addChildren()
+ *
* @param {NodeData} node node definition
- * @param {String} [mode] 'before', 'after', or 'child'
+ * @param {string} [mode=child] 'before', 'after', or 'child' ('over' is a synonym for 'child')
* @returns {FancytreeNode} new node
*/
addNode: function(node, mode){
if(mode === undefined || mode === "over"){
mode = "child";
@@ -366,14 +367,14 @@
return this.addChildren(node);
}
_assert(false, "Invalid mode: " + mode);
},
/**
+ * Modify existing child nodes.
*
* @param {NodePatch} patch
* @returns {$.Promise}
- * @see {@link applyPatch} to modify existing child nodes.
* @see FancytreeNode#addChildren
*/
applyPatch: function(patch) {
// patch [key, null] means 'remove'
if(patch === null){
@@ -423,20 +424,20 @@
return this.tree._callHook("nodeCollapseSiblings", this);
},
/** Copy this node as sibling or child of `node`.
*
* @param {FancytreeNode} node source node
- * @param {String} mode 'before' | 'after' | 'child'
+ * @param {string} mode 'before' | 'after' | 'child'
* @param {Function} [map] callback function(NodeData) that could modify the new node
* @returns {FancytreeNode} new
*/
copyTo: function(node, mode, map) {
return node.addNode(this.toDict(true, map), mode);
},
/** Count direct and indirect children.
*
- * @param {Boolean} [deep=true] pass 'false' to only count direct children
+ * @param {boolean} [deep=true] pass 'false' to only count direct children
* @returns {int} number of child nodes
*/
countChildren: function(deep) {
var cl = this.children, i, l, n;
if( !cl ){
@@ -469,11 +470,11 @@
}
},
// TODO: expand(flag)
/**Find all nodes that contain `match` in the title.
*
- * @param {String | function(node)} match string to search for, of a function that
+ * @param {string | function(node)} match string to search for, of a function that
* returns `true` if a node is matched.
* @returns {FancytreeNode[]} array of nodes (may be empty)
* @see FancytreeNode#findAll
*/
findAll: function(match) {
@@ -486,11 +487,11 @@
});
return res;
},
/**Find first node that contains `match` in the title (not including self).
*
- * @param {String | function(node)} match string to search for, of a function that
+ * @param {string | function(node)} match string to search for, of a function that
* returns `true` if a node is matched.
* @returns {FancytreeNode} matching node or null
* @example
* <b>fat</b> text
*/
@@ -671,22 +672,22 @@
/** @returns {int} 0-based child index.*/
getIndex: function() {
// return this.parent.children.indexOf(this);
return $.inArray(this, this.parent.children); // indexOf doesn't work in IE7
},
- /**@returns {String} hierarchical child index (1-based: '3.2.4').*/
+ /**@returns {string} hierarchical child index (1-based: '3.2.4').*/
getIndexHier: function(separator) {
separator = separator || ".";
var res = [];
$.each(this.getParentList(false, true), function(i, o){
res.push(o.getIndex() + 1);
});
return res.join(separator);
},
/**
- * @param {Boolean} [excludeSelf=false]
- * @returns {String} parent keys separated by options.keyPathSeparator
+ * @param {boolean} [excludeSelf=false]
+ * @returns {string} parent keys separated by options.keyPathSeparator
*/
getKeyPath: function(excludeSelf) {
var path = [],
sep = this.tree.options.keyPathSeparator;
this.visitParents(function(n){
@@ -729,12 +730,12 @@
getParent: function() {
// TODO: return null for top-level nodes?
return this.parent;
},
/**
- * @param {Boolean} [includeRoot=false]
- * @param {Boolean} [includeSelf=false]
+ * @param {boolean} [includeRoot=false]
+ * @param {boolean} [includeSelf=false]
* @returns {FancytreeNode[]}
*/
getParentList: function(includeRoot, includeSelf) {
var l = [],
dtn = includeSelf ? this : this.parent;
@@ -775,28 +776,28 @@
}
return true;
}
return !!this.children;
},
- /**@returns {Boolean} true, if node has keyboard focus*/
+ /**@returns {boolean} true, if node has keyboard focus*/
hasFocus: function() {
return (this.tree.hasFocus() && this.tree.focusNode === this);
},
- /**@returns {Boolean} true, if node is active*/
+ /**@returns {boolean} true, if node is active*/
isActive: function() {
return (this.tree.activeNode === this);
},
/**
* @param {FancytreeNode} otherNode
- * @returns {Boolean} true, if node is a direct child of otherNode
+ * @returns {boolean} true, if node is a direct child of otherNode
*/
isChildOf: function(otherNode) {
return (this.parent && this.parent === otherNode);
},
/**
* @param {FancytreeNode} otherNode
- * @returns {Boolean} true, if node is a sub node of otherNode
+ * @returns {boolean} true, if node is a sub node of otherNode
*/
isDescendantOf: function(otherNode) {
if(!otherNode || otherNode.tree !== this.tree){
return false;
}
@@ -807,41 +808,41 @@
}
p = p.parent;
}
return false;
},
- /** @returns {Boolean} true, if node is expanded*/
+ /** @returns {boolean} true, if node is expanded*/
isExpanded: function() {
return !!this.expanded;
},
- /** @returns {Boolean}*/
+ /** @returns {boolean}*/
isFirstSibling: function() {
var p = this.parent;
return !p || p.children[0] === this;
},
- /** @returns {Boolean}*/
+ /** @returns {boolean}*/
isFolder: function() {
return !!this.folder;
},
- /** @returns {Boolean}*/
+ /** @returns {boolean}*/
isLastSibling: function() {
var p = this.parent;
return !p || p.children[p.children.length-1] === this;
},
- /** @returns {Boolean} true, if node is lazy (even if data was already loaded)*/
+ /** @returns {boolean} true, if node is lazy (even if data was already loaded)*/
isLazy: function() {
return !!this.lazy;
},
- /** @returns {Boolean} true, if children are currently beeing loaded*/
+ /** @returns {boolean} true, if children are currently beeing loaded*/
isLoading: function() {
_raiseNotImplemented(); // TODO: implement
},
- /**@returns {Boolean} true, if node is the (invisible) system root node*/
+ /**@returns {boolean} true, if node is the (invisible) system root node*/
isRoot: function() {
return (this.tree.rootNode === this);
},
- /** @returns {Boolean} true, if node is selected (e.g. has a checkmark set)*/
+ /** @returns {boolean} true, if node is selected (e.g. has a checkmark set)*/
isSelected: function() {
return !!this.selected;
},
// TODO: use _isStatusNode as class attribute name
// isStatusNode: function() {
@@ -870,11 +871,11 @@
parents[i].setExpanded(true);
}
},
/** Move this node to targetNode.
* @param {FancytreeNode} targetNode
- * @param {String} mode
+ * @param {string} mode
* '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'.
* 'before': add this node as sibling before targetNode.
* 'after': add this node as sibling after targetNode.
@@ -891,11 +892,11 @@
if(this === targetNode){
return;
}else if( !this.parent ){
throw "Cannot move system root";
}else if( targetParent.isDescendantOf(this) ){
- throw "Cannot move a node to it's own descendant";
+ throw "Cannot move a node to its own descendant";
}
// Unlink this node from current parent
if( this.parent.children.length === 1 ) {
this.parent.children = this.parent.lazy ? [] : null;
this.parent.expanded = false;
@@ -961,14 +962,14 @@
// TODO: fix selection state and activation, ...
n.tree = targetNode.tree;
}, true);
}
- // A collaposed node won't re-render children, so we have to remove it manually
- if( !targetParent.expanded){
- prevParent.ul.removeChild(this.li);
- }
+ // A collaposed node won't re-render children, so we have to remove it manually
+ // if( !targetParent.expanded ){
+ // prevParent.ul.removeChild(this.li);
+ // }
// Update HTML markup
if( !prevParent.isDescendantOf(targetParent)) {
prevParent.render();
}
@@ -1029,10 +1030,16 @@
// Navigate to node
function _goto(n){
if( n ){
n.makeVisible();
+ // Node may still be hidden by a filter
+ if( ! $(n.span).is(":visible") ) {
+ n.debug("Navigate: skipping hidden node");
+ n.navigate(where, activate);
+ return;
+ }
return activate === false ? n.setFocus() : n.setActive();
}
}
switch( where ) {
@@ -1042,24 +1049,20 @@
}
break;
case KC.LEFT:
if( this.expanded ) {
this.setExpanded(false);
-// tree.nodeSetFocus(ctx);
_goto(this);
} else if( this.parent && this.parent.parent ) {
-// this.parent.setFocus();
_goto(this.parent);
}
break;
case KC.RIGHT:
if( !this.expanded && (this.children || this.lazy) ) {
this.setExpanded();
-// tree.nodeSetFocus(ctx);
_goto(this);
} else if( this.children && this.children.length ) {
-// this.children[0].setFocus();
_goto(this.children[0]);
}
break;
case KC.UP:
sib = this.getPrevSibling();
@@ -1087,11 +1090,11 @@
handled = false;
}
},
/**
* Discard and reload all children of a lazy node.
- * @param {Boolean} [discard=false]
+ * @param {boolean} [discard=false]
* @returns $.Promise
*/
lazyLoad: function(discard) {
if(discard || this.hasChildren() === undefined){
this.discard();
@@ -1100,23 +1103,23 @@
var source = this.tree._triggerNodeEvent("lazyload", this);
_assert(typeof source !== "boolean", "lazyload event must return source in data.result");
return this.tree._callHook("nodeLoadChildren", this, source);
},
/**
- * @see Fancytree#nodeRender
+ * @see Fancytree_Hooks#nodeRender
*/
render: function(force, deep) {
return this.tree._callHook("nodeRender", this, force, deep);
},
/**
- * @see Fancytree#nodeRenderTitle
+ * @see Fancytree_Hooks#nodeRenderTitle
*/
renderTitle: function() {
return this.tree._callHook("nodeRenderTitle", this);
},
/**
- * @see Fancytree#nodeRenderStatus
+ * @see Fancytree_Hooks#nodeRenderStatus
*/
renderStatus: function() {
return this.tree._callHook("nodeRenderStatus", this);
},
/** Remove this node (not allowed for root).*/
@@ -1163,11 +1166,11 @@
}
// this.tree.debug("setTimeout(%s, %s): %s", mode, ms, this.tree.timer);
},
/**
*
- * @param {Boolean | PlainObject} [effects=false] animation options.
+ * @param {boolean | PlainObject} [effects=false] animation options.
* @param {FancytreeNode} [topNode=null] this node will remain visible in
* any case, even if `this` is outside the scroll pane.
* @returns $.Promise
*/
scrollIntoView: function(effects, topNode) {
@@ -1197,11 +1200,11 @@
// If a topNode was passed, make sure that it is never scrolled
// outside the upper border
if(topNode){
topNodeY = topNode ? $(topNode.span).position().top : 0;
if((nodeY - topNodeY) > containerHeight){
- newScrollTop = scrollTop + nodeY;
+ newScrollTop = scrollTop + topNodeY;
}
}
}
if(newScrollTop !== null){
if(effects){
@@ -1234,31 +1237,34 @@
}
*/
},
/**Activate this node.
- * @param {Boolean} [flag=true] pass false to deactivate
+ * @param {boolean} [flag=true] pass false to deactivate
+ * @param {object} [opts] additional options. Defaults to {noEvents: false}
*/
- setActive: function(flag){
- return this.tree._callHook("nodeSetActive", this, flag);
+ setActive: function(flag, opts){
+ return this.tree._callHook("nodeSetActive", this, flag, opts);
},
- /**Expand this node.
- * @param {Boolean} [flag=true] pass false to collapse
+ /**Expand or collapse this node.
+ * @param {boolean} [flag=true] pass false to collapse
+ * @param {object} [opts] additional options. Defaults to {noAnimation: false, noEvents: false}
+ * @returns {$.Promise} resolved, when lazy loading and animations are done
*/
- setExpanded: function(flag){
- return this.tree._callHook("nodeSetExpanded", this, flag);
+ setExpanded: function(flag, opts){
+ return this.tree._callHook("nodeSetExpanded", this, flag, opts);
},
/**Set keyboard focus to this node.
- * @param {Boolean} [flag=true] pass false to blur
+ * @param {boolean} [flag=true] pass false to blur
* @see Fancytree#setFocus
*/
setFocus: function(flag){
return this.tree._callHook("nodeSetFocus", this, flag);
},
// TODO: setLazyNodeStatus
/**Select this node.
- * @param {Boolean} [flag=true] pass false to deselect
+ * @param {boolean} [flag=true] pass false to deselect
*/
setSelected: function(flag){
return this.tree._callHook("nodeSetSelected", this, flag);
},
/**Rename this node.
@@ -1267,12 +1273,12 @@
setTitle: function(title){
this.title = title;
this.renderTitle();
},
/**Sort child list by title.
- * @param {function} [cmd] custom compare function.
- * @param {Boolean} [deep] pass true to sort all descendant nodes
+ * @param {function} [cmp] custom compare function(a, b) that returns -1, 0, or 1 (defaults to sort by title).
+ * @param {boolean} [deep=false] pass true to sort all descendant nodes
*/
sortChildren: function(cmp, deep) {
var i,l,
cl = this.children;
@@ -1294,25 +1300,24 @@
}
if( deep !== "$norender$" ){
this.render();
}
},
- /** Convert node (or whole branch) into a dictionary.
+ /** Convert node (or whole branch) into a plain object.
*
* The result is compatible with node.addChildren().
*
- * @param {Boolean} recursive
- * @param {function} callback callback(dict) is called for every dict (), in order to allow modifications
- * @returns {NodePatch}
+ * @param {boolean} recursive
+ * @param {function} callback callback(dict) is called for every node, in order to allow modifications
+ * @returns {NodeData}
*/
toDict: function(recursive, callback) {
var i, l, node,
dict = {},
self = this;
$.each(NODE_ATTRS, function(i, a){
-// if(self[a] !== undefined && self[a] !== null){
if(self[a] || self[a] === false){
dict[a] = self[a];
}
});
if(!$.isEmptyObject(this.data)){
@@ -1352,12 +1357,12 @@
},
/** Call fn(node) for all child nodes. Stop iteration, if fn() returns false.
* Skip current branch, if fn() returns 'skip'.
* @param {function} fn the callback function.
* Return false to stop iteration, return "skip" to skip this node and children only.
- * @param {Boolean} [includeSelf=false]
- * @returns {Boolean} false, if the iterator was stopped.
+ * @param {boolean} [includeSelf=false]
+ * @returns {boolean} false, if the iterator was stopped.
*/
visit: function(fn, includeSelf) {
var i, l,
res = true,
children = this.children;
@@ -1380,11 +1385,11 @@
},
/**
*
* @param fn
* @param includeSelf
- * @returns {Boolean}
+ * @returns {boolean}
*/
visitParents: function(fn, includeSelf) {
// Visit parent nodes (bottom up)
if(includeSelf && fn(this) === false){
return false;
@@ -1411,14 +1416,16 @@
/* *****************************************************************************
* Fancytree
*/
/**
- * Construct a new tree.
- * @class The controller behind a fancytree.
- * @name Fancytree
- * @constructor
+ * Construct a new tree object.
+ *
+ * @class Fancytree
+ * @classdesc A Fancytree is the controller behind a fancytree.
+ * This class also contains 'hook methods': see {@link Fancytree_Hooks}.
+ *
* @param {Widget} widget
*
* @property {FancytreeOptions} options
* @property {FancytreeNode} rootNode
* @property {FancytreeNode} activeNode
@@ -1426,19 +1433,18 @@
* @property {jQueryObject} $div
* @property {object} widget
* @property {object} ext
* @property {object} data
* @property {object} options
- * @property {String} _id
- * @property {String} statusClassPropName
- * @property {String} ariaPropName
- * @property {String} nodeContainerAttrName
- * @property {String} $container
+ * @property {string} _id
+ * @property {string} statusClassPropName
+ * @property {string} ariaPropName
+ * @property {string} nodeContainerAttrName
+ * @property {string} $container
* @property {FancytreeNode} lastSelectedNode
*/
-function Fancytree(widget){
- // TODO: rename widget to widget (it's not a jQuery object)
+function Fancytree(widget) {
this.widget = widget;
this.$div = widget.element;
this.options = widget.options;
this.ext = {}; // Active extension instances
this.data = {};
@@ -1461,11 +1467,12 @@
var fakeParent = { tree: this },
$ul;
this.rootNode = new FancytreeNode(fakeParent, {
title: "root",
key: "root_" + this._id,
- children: null
+ children: null,
+ expanded: true
});
this.rootNode.parent = null;
// Create root markup
$ul = $("<ul>", {
@@ -1486,12 +1493,12 @@
.attr("aria-multiselectable", true);
}
}
-Fancytree.prototype = /**@lends Fancytree*/{
- /** Return a context object that can be re-used for _callHook().
+Fancytree.prototype = /** @lends Fancytree# */{
+ /* Return a context object that can be re-used for _callHook().
* @param {Fancytree | FancytreeNode | EventData} obj
* @param {Event} originalEvent
* @param {Object} extra
* @returns {EventData}
*/
@@ -1516,15 +1523,15 @@
if(extra){
$.extend(ctx, extra);
}
return ctx;
},
- /** Trigger a hook function: funcName(ctx, [...]).
+ /* Trigger a hook function: funcName(ctx, [...]).
*
- * @param {String} funcName
+ * @param {string} funcName
* @param {Fancytree|FancytreeNode|EventData} contextObject
- * @param {any, ...} [_extraArgs] optional additional arguments
+ * @param {any} [_extraArgs] optional additional arguments
* @returns {any}
*/
_callHook: function(funcName, contextObject, _extraArgs) {
var ctx = this._makeHookContext(contextObject),
fn = this[funcName],
@@ -1534,15 +1541,51 @@
}
args.unshift(ctx);
// this.debug("_hook", funcName, ctx.node && ctx.node.toString() || ctx.tree.toString(), args);
return fn.apply(this, args);
},
- /** Activate node with a given key.
+ /* Check if current extensions dependencies are met and throw an error if not.
*
+ * This method may be called inside the `treeInit` hook for custom extensions.
+ *
+ * @param {string} extension name of the required extension
+ * @param {boolean} [required=true] pass `false` if the extension is optional, but we want to check for order if it is present
+ * @param {boolean} [before] `true` if `name` must be included before this, `false` otherwise (use `null` if order doesn't matter)
+ * @param {string} [message] optional error message (defaults to a descriptve error message)
+ */
+ _requireExtension: function(name, required, before, message) {
+ before = !!before;
+ var thisName = this._local.name,
+ extList = this.options.extensions,
+ isBefore = $.inArray(name, extList) < $.inArray(thisName, extList),
+ isMissing = required && this.ext[name] == null,
+ badOrder = !isMissing && before != null && (before !== isBefore);
+
+ _assert(thisName && thisName !== name);
+
+ if( isMissing || badOrder ){
+ if( !message ){
+ if( isMissing || required ){
+ message = "'" + thisName + "' extension requires '" + name + "'";
+ if( badOrder ){
+ message += " to be registered " + (before ? "before" : "after") + " itself";
+ }
+ }else{
+ message = "If used together, `" + name + "` must be registered " + (before ? "before" : "after") + " `" + thisName + "`";
+ }
+ }
+ $.error(message);
+ return false;
+ }
+ return true;
+ },
+ /** Activate node with a given key and fire focus and activate events.
+ *
* A prevously activated node will be deactivated.
+ * If activeVisible option is set, all parents will be expanded as necessary.
* Pass key = false, to deactivate the current node only.
- * @param {String} key
+ * @param {string} key
* @returns {FancytreeNode} activated node (null, if not found)
*/
activateKey: function(key) {
var node = this.getNodeByKey(key);
if(node){
@@ -1550,11 +1593,11 @@
}else if(this.activeNode){
this.activeNode.setActive(false);
}
return node;
},
- /**
+ /** (experimental)
*
* @param {Array} patchList array of [key, NodePatch] arrays
* @returns {$.Promise} resolved, when all patches have been applied
* @see TreePatch
*/
@@ -1586,15 +1629,17 @@
if(dd){
dd.cancel();
}
},
*/
- /** Return the number of child nodes. */
+ /** Return the number of nodes.
+ * @returns {integer}
+ */
count: function() {
return this.rootNode.countChildren();
},
- /** Write to browser console if debugLevel >= 2 (prepending tree info)
+ /** Write to browser console if debugLevel >= 2 (prepending tree name)
*
* @param {*} msg string or object or array of such
*/
debug: function(msg){
if( this.options.debugLevel >= 2 ) {
@@ -1609,12 +1654,12 @@
/**
* Generate INPUT elements that can be submitted with html forms.
*
* In selectMode 3 only the topmost selected nodes are considered.
*
- * @param {Boolean | String} [selected=true]
- * @param {Boolean | String} [active=true]
+ * @param {boolean | string} [selected=true]
+ * @param {boolean | string} [active=true]
*/
generateFormElements: function(selected, active) {
// TODO: test case
var nodeList,
selectedName = (selected !== false) ? "ft_" + this._id : selected,
@@ -1648,32 +1693,34 @@
checked: true
}));
}
},
/**
- * Return node that is active.
+ * Return the currently active FancytreeNode or null.
* @returns {FancytreeNode}
*/
getActiveNode: function() {
return this.activeNode;
},
- /** @returns {FancytreeNode | null}*/
+ /** Return the first top level node if any (not the invisible root node).
+ * @returns {FancytreeNode | null}
+ */
getFirstChild: function() {
return this.rootNode.getFirstChild();
},
/**
* Return node that has keyboard focus.
- * @param {Boolean} [ifTreeHasFocus=false]
+ * @param {boolean} [ifTreeHasFocus=false] (not yet implemented)
* @returns {FancytreeNode}
*/
getFocusNode: function(ifTreeHasFocus) {
// TODO: implement ifTreeHasFocus
return this.focusNode;
},
/**
- * Return node with a given key.
- * @param {String} key
+ * Return node with a given key or null if not found.
+ * @param {string} key
* @param {FancytreeNode} [searchRoot] only search below this node
* @returns {FancytreeNode | null}
*/
getNodeByKey: function(key, searchRoot) {
// Search the DOM by element ID (assuming this is faster than traversing all nodes).
@@ -1699,12 +1746,12 @@
}, true);
return match;
},
// TODO: getRoot()
/**
- * Return a list of selected nodes.
- * @param {Boolean} [stopOnParents=false] only return the topmost selected
+ * Return an array of selected nodes.
+ * @param {boolean} [stopOnParents=false] only return the topmost selected
* node (useful with selectMode 3)
* @returns {FancytreeNode[]}
*/
getSelectedNodes: function(stopOnParents) {
var nodeList = [];
@@ -1716,18 +1763,17 @@
}
}
});
return nodeList;
},
- /**
- * @returns {Boolean} true if the tree control has keyboard focus
+ /** Return true if the tree control has keyboard focus
+ * @returns {boolean}
*/
hasFocus: function(){
return !!this._hasFocus;
},
- /** Write to browser console if debugLevel >= 1 (prepending tree info)
- *
+ /** Write to browser console if debugLevel >= 1 (prepending tree name)
* @param {*} msg string or object or array of such
*/
info: function(msg){
if( this.options.debugLevel >= 1 ) {
Array.prototype.unshift.call(arguments, this.toString());
@@ -1743,73 +1789,26 @@
},
TODO: isUserEvent: function() {
return ( this.phase=="userEvent" );
},
*/
- /**
- * Expand all parents of one or more nodes.
- * Calls
- * @param {String | String[]} keyPath one or more key paths (e.g. '/3/2_1/7')
- * @param {function} callback callbeck(mode) is called for every visited node
- * @returns {$.Promise}
- */
- /*
- _loadKeyPath: function(keyPath, callback) {
- var tree = this.tree;
- tree.logDebug("%s._loadKeyPath(%s)", this, keyPath);
- if(keyPath === ""){
- throw "Key path must not be empty";
- }
- var segList = keyPath.split(tree.options.keyPathSeparator);
- if(segList[0] === ""){
- throw "Key path must be relative (don't start with '/')";
- }
- var seg = segList.shift();
- for(var i=0, l=this.childList.length; i < l; i++){
- var child = this.childList[i];
- if( child.data.key === seg ){
- if(segList.length === 0) {
- // Found the end node
- callback.call(tree, child, "ok");
-
- }else if(child.data.isLazy && (child.childList === null || child.childList === undefined)){
- tree.logDebug("%s._loadKeyPath(%s) -> reloading %s...", this, keyPath, child);
- var self = this;
- child.reloadChildren(function(node, isOk){
- // After loading, look for direct child with that key
- if(isOk){
- tree.logDebug("%s._loadKeyPath(%s) -> reloaded %s.", node, keyPath, node);
- callback.call(tree, child, "loaded");
- node._loadKeyPath(segList.join(tree.options.keyPathSeparator), callback);
- }else{
- tree.logWarning("%s._loadKeyPath(%s) -> reloadChildren() failed.", self, keyPath);
- callback.call(tree, child, "error");
- }
- }); // Note: this line gives a JSLint warning (Don't make functions within a loop)
- // we can ignore it, since it will only be exectuted once, the the loop is ended
- // See also http://stackoverflow.com/questions/3037598/how-to-get-around-the-jslint-error-dont-make-functions-within-a-loop
- } else {
- callback.call(tree, child, "loaded");
- // Look for direct child with that key
- child._loadKeyPath(segList.join(tree.options.keyPathSeparator), callback);
- }
- return;
- }
- }
- // Could not find key
- tree.logWarning("Node not found: " + seg);
- return;
- },
-
- */
-
/**
- * Expand all parents of one or more nodes.
- * Calls
- * @param {String | String[]} keyPathList one or more key paths (e.g. '/3/2_1/7')
- * @param {function} callback callbeck(mode) is called for every visited node ('loaded', 'ok', 'error')
+ * Make sure that a node with a given ID is loaded, by traversing - and
+ * loading - its parents. This method is ment for lazy hierarchies.
+ * A callback is executed for every node as we go.
+ * @example
+ * tree.loadKeyPath("/_3/_23/_26/_27", function(node, status){
+ * if(status === "loaded") {
+ * console.log("loaded intermiediate node " + node);
+ * }else if(status === "ok") {
+ * node.activate();
+ * }
+ * });
+ *
+ * @param {string | string[]} keyPathList one or more key paths (e.g. '/3/2_1/7')
+ * @param {function} callback callback(node, status) is called for every visited node ('loading', 'loaded', 'ok', 'error')
* @returns {$.Promise}
*/
loadKeyPath: function(keyPathList, callback, _rootNode) {
var deferredList, dfd, i, path, key, loadMap, node, segList,
root = _rootNode || this.rootNode,
@@ -1879,10 +1878,114 @@
__lazyload(key, node, dfd);
}
// Return a promise that is resovled, when ALL paths were loaded
return $.when.apply($, deferredList).promise();
},
+ /** Re-fire beforeActivate and activate events. */
+ reactivate: function(setFocus) {
+ var node = this.activeNode;
+ if( node ) {
+ this.activeNode = null; // Force re-activating
+ node.setActive();
+ if( setFocus ){
+ node.setFocus();
+ }
+ }
+ },
+ /** Reload tree from source and return a promise.
+ * @param [source] optional new source (defaults to initial source data)
+ * @returns {$.Promise}
+ */
+ reload: function(source) {
+ this._callHook("treeClear", this);
+ return this._callHook("treeLoad", this, source);
+ },
+ /**Render tree (i.e. create DOM elements for all top-level nodes).
+ * @param {boolean} [force=false] create DOM elemnts, even is parent is collapsed
+ * @param {boolean} [deep=false]
+ */
+ render: function(force, deep) {
+ return this.rootNode.render(force, deep);
+ },
+ // TODO: selectKey: function(key, select)
+ // TODO: serializeArray: function(stopOnParents)
+ /**
+ * @param {boolean} [flag=true]
+ */
+ setFocus: function(flag) {
+ return this._callHook("treeSetFocus", this, flag);
+ },
+ /**
+ * Return all nodes as nested list of {@link NodeData}.
+ *
+ * @param {boolean} [includeRoot=false] Returns the hidden system root node (and its children)
+ * @param {function} [callback(node)] Called for every node
+ * @returns {Array | object}
+ * @see FancytreeNode#toDict
+ */
+ toDict: function(includeRoot, callback){
+ var res = this.rootNode.toDict(true, callback);
+ return includeRoot ? res : res.children;
+ },
+ /* Implicitly called for string conversions.
+ * @returns {string}
+ */
+ toString: function(){
+ return "<Fancytree(#" + this._id + ")>";
+ },
+ /* _trigger a widget event with additional node ctx.
+ * @see EventData
+ */
+ _triggerNodeEvent: function(type, node, originalEvent, extra) {
+// this.debug("_trigger(" + type + "): '" + ctx.node.title + "'", ctx);
+ var ctx = this._makeHookContext(node, originalEvent, extra),
+ res = this.widget._trigger(type, originalEvent, ctx);
+ if(res !== false && ctx.result !== undefined){
+ return ctx.result;
+ }
+ return res;
+ },
+ /* _trigger a widget event with additional tree data. */
+ _triggerTreeEvent: function(type, originalEvent) {
+// this.debug("_trigger(" + type + ")", ctx);
+ var ctx = this._makeHookContext(this, originalEvent),
+ res = this.widget._trigger(type, originalEvent, ctx);
+
+ if(res !== false && ctx.result !== undefined){
+ return ctx.result;
+ }
+ return res;
+ },
+ /** Call fn(node) for all nodes.
+ *
+ * @param {function} fn the callback function.
+ * Return false to stop iteration, return "skip" to skip this node and children only.
+ * @returns {boolean} false, if the iterator was stopped.
+ */
+ visit: function(fn) {
+ return this.rootNode.visit(fn, false);
+ },
+ /** Write warning to browser console (prepending tree info)
+ *
+ * @param {*} msg string or object or array of such
+ */
+ warn: function(msg){
+ Array.prototype.unshift.call(arguments, this.toString());
+ consoleApply("warn", arguments);
+ }
+};
+
+/**
+ * These additional methods of the {@link Fancytree} class are 'hook functions'
+ * that can be used and overloaded by extensions.
+ * (See <a href="https://github.com/mar10/fancytree/wiki/TutorialExtensions">writing extensions</a>.)
+ * @mixin Fancytree_Hooks
+ */
+$.extend(Fancytree.prototype,
+ /** @lends Fancytree_Hooks# */
+ {
+
/** _Default handling for mouse click events. */
nodeClick: function(ctx) {
// this.tree.logDebug("ftnode.onClick(" + event.type + "): ftnode:" + this + ", button:" + event.button + ", which: " + event.which);
var activate, expand,
event = ctx.originalEvent,
@@ -1932,20 +2035,20 @@
if(event.target.localName === "a" && event.target.className === "fancytree-title"){
event.preventDefault();
}
// TODO: return promise?
},
- nodeCollapseSiblings: function(ctx) {
+ nodeCollapseSiblings: function(ctx, callOpts) {
// TODO: return promise?
var ac, i, l,
node = ctx.node;
if( node.parent ){
ac = node.parent.children;
for (i=0, l=ac.length; i<l; i++) {
if ( ac[i] !== node && ac[i].expanded ){
- this._callHook("nodeSetExpanded", ac[i], false);
+ this._callHook("nodeSetExpanded", ac[i], false, callOpts);
}
}
}
},
nodeDblclick: function(ctx) {
@@ -2164,11 +2267,11 @@
parents[i].setExpanded(true);
}
},
/**
* Remove a single direct child of ctx.node.
- * @param ctx
+ * @param {EventData} ctx
* @param {FancytreeNode} childNode dircect child of ctx.node
*/
nodeRemoveChild: function(ctx, childNode) {
var idx,
node = ctx.node,
@@ -2210,15 +2313,19 @@
var node = ctx.node;
FT.debug("nodeRemoveChildMarkup()", node.toString());
// TODO: Unlink attr.ftnode to support GC
if(node.ul){
- $(node.ul).remove();
+ if( node.isRoot() ) {
+ $(node.ul).empty();
+ } else {
+ $(node.ul).remove();
+ node.ul = null;
+ }
node.visit(function(n){
n.li = n.ul = null;
});
- node.ul = null;
}
},
/**Remove all descendants of ctx.node.
* @param {EventData} ctx
*/
@@ -2294,13 +2401,13 @@
* </ul>
* </li>
* </code>
*
* @param: {EventData} ctx
- * @param: {Boolean} [force=false] re-render, even if html markup was already created
- * @param: {Boolean} [deep=false] also render all descendants, even if parent is collapsed
- * @param: {Boolean} [collapsed=false] force root node to be collapsed, so we can apply animated expand later
+ * @param: {boolean} [force=false] re-render, even if html markup was already created
+ * @param: {boolean} [deep=false] also render all descendants, even if parent is collapsed
+ * @param: {boolean} [collapsed=false] force root node to be collapsed, so we can apply animated expand later
*/
nodeRender: function(ctx, force, deep, collapsed, _recursive) {
/* This method must take care of all cases where the current data mode
* (i.e. node hierarchy) does not match the current markup.
*
@@ -2308,27 +2415,35 @@
* create markup
* - node was rendered: exit fast
* - children have been added
* - childern have been removed
*/
- var childLI, childNode1, childNode2, i, l, subCtx,
+ var childLI, childNode1, childNode2, i, l, next, subCtx,
node = ctx.node,
tree = ctx.tree,
opts = ctx.options,
aria = opts.aria,
firstTime = false,
parent = node.parent,
isRootNode = !parent,
children = node.children;
-// FT.debug("nodeRender(" + !!force + ", " + !!deep + ")", node.toString());
+ FT.debug("nodeRender(" + !!force + ", " + !!deep + ")", node.toString());
if( ! isRootNode && ! parent.ul ) {
// issue #105: calling node.collapse on a deep, unrendered node
return;
}
_assert(isRootNode || parent.ul, "parent UL must exist");
+// if(node.li && (force || (node.li.parentNode !== node.parent.ul) ) ){
+// if(node.li.parentNode !== node.parent.ul){
+// // alert("unlink " + node + " (must be child of " + node.parent + ")");
+// this.warn("unlink " + node + " (must be child of " + node.parent + ")");
+// }
+// // this.debug("nodeRemoveMarkup...");
+// this.nodeRemoveMarkup(ctx);
+// }
// Render the node
if( !isRootNode ){
// Discard markup on force-mode, or if it is not linked to parent <ul>
if(node.li && (force || (node.li.parentNode !== node.parent.ul) ) ){
if(node.li.parentNode !== node.parent.ul){
@@ -2358,29 +2473,22 @@
node.span.className = "fancytree-node";
if(aria){
$(node.span).attr("aria-labelledby", "ftal_" + node.key);
}
node.li.appendChild(node.span);
- // Note: we don't add the LI to the DOM know, but only after we
- // added all sub elements (hoping that this performs better since
- // the browser only have to render once)
- // TODO: benchmarks to prove this
-// parent.ul.appendChild(node.li);
// Create inner HTML for the <span> (expander, checkbox, icon, and title)
this.nodeRenderTitle(ctx);
// Allow tweaking and binding, after node was created for the first time
-// tree._triggerNodeEvent("createNode", ctx);
if ( opts.createNode ){
opts.createNode.call(tree, {type: "createNode"}, ctx);
}
}else{
// this.nodeRenderTitle(ctx);
}
// Allow tweaking after node state was rendered
-// tree._triggerNodeEvent("renderNode", ctx);
if ( opts.renderNode ){
opts.renderNode.call(tree, {type: "renderNode"}, ctx);
}
}
@@ -2406,12 +2514,24 @@
// Add child markup
for(i=0, l=children.length; i<l; i++) {
subCtx = $.extend({}, ctx, {node: children[i]});
this.nodeRender(subCtx, force, deep, false, true);
}
+ // Remove <li> if nodes have moved to another parent
+ childLI = node.ul.firstChild;
+ while( childLI ){
+ childNode2 = childLI.ftnode;
+ if( childNode2 && childNode2.parent !== node ) {
+ node.debug("_fixParent: remove missing " + childNode2, childLI);
+ next = childLI.nextSibling;
+ childLI.parentNode.removeChild(childLI);
+ childLI = next;
+ }else{
+ childLI = childLI.nextSibling;
+ }
+ }
// Make sure, that <li> order matches node.children order.
-// this.nodeFixOrder(ctx);
childLI = node.ul.firstChild;
for(i=0, l=children.length-1; i<l; i++) {
childNode1 = children[i];
childNode2 = childLI.ftnode;
if( childNode1 !== childNode2 ) {
@@ -2419,11 +2539,10 @@
node.ul.insertBefore(childNode1.li, childNode2.li);
} else {
childLI = childLI.nextSibling;
}
}
- // TODO: need to check, if node.ul has <li>s, that are not in node.children[] ?
}
}else{
// No children: remove markup if any
if( node.ul ){
// alert("remove child markup for " + node);
@@ -2437,18 +2556,17 @@
// Finally add the whole structure to the DOM, so the browser can render
if(firstTime){
parent.ul.appendChild(node.li);
}
}
- return;
},
/** Create HTML for the node's outer <span> (expander, checkbox, icon, and title).
* @param {EventData} ctx
*/
nodeRenderTitle: function(ctx, title) {
// set node connector images, links and text
- var id, imageSrc, nodeTitle, role, tooltip,
+ var id, imageSrc, nodeTitle, role, tabindex, tooltip,
node = ctx.node,
tree = ctx.tree,
opts = ctx.options,
aria = opts.aria,
level = node.getLevel(),
@@ -2511,20 +2629,17 @@
if(!nodeTitle){
// TODO: escape tooltip string
tooltip = node.tooltip ? " title='" + node.tooltip.replace(/\"/g, """) + "'" : "";
id = aria ? " id='ftal_" + node.key + "'" : "";
role = aria ? " role='treeitem'" : "";
-// href = node.data.href || "#";
-// if( opts.nolink || node.nolink ) {
-// nodeTitle = "<span role='treeitem' tabindex='-1' class='fancytree-title'" + id + tooltip + ">" + node.title + "</span>";
- nodeTitle = "<span " + role + " class='fancytree-title'" + id + tooltip + ">" + node.title + "</span>";
-// } else {
-// nodeTitle = "<a href='" + href + "' tabindex='-1' class='fancytree-title'" + tooltip + ">" + node.title + "</a>";
-// }
+ tabindex = opts.titlesTabbable ? " tabindex='0'" : "";
+
+ nodeTitle = "<span " + role + " class='fancytree-title'" + id + tooltip + tabindex + ">" + node.title + "</span>";
}
ares.push(nodeTitle);
// Note: this will trigger focusout, if node had the focus
+ //$(node.span).html(ares.join("")); // it will cleanup the jQuery data currently associated with SPAN (if any), but it executes more slowly
node.span.innerHTML = ares.join("");
},
/** Update element classes according to node state.
* @param {EventData} ctx
*/
@@ -2637,20 +2752,23 @@
/** Activate node.
* flag defaults to true.
* If flag is true, the node is activated (must be a synchronous operation)
* If flag is false, the node is deactivated (must be a synchronous operation)
* @param {EventData} ctx
- * @param {Boolean} [flag=true]
+ * @param {boolean} [flag=true]
+ * @param {object} [opts] additional options. Defaults to {}
*/
- nodeSetActive: function(ctx, flag) {
+ nodeSetActive: function(ctx, flag, callOpts) {
// Handle user click / [space] / [enter], according to clickFolderMode.
+ callOpts = callOpts || {};
var subCtx,
node = ctx.node,
tree = ctx.tree,
opts = ctx.options,
// userEvent = !!ctx.originalEvent,
isActive = (node === tree.activeNode);
+
// flag defaults to true
flag = (flag !== false);
node.debug("nodeSetActive", flag);
if(isActive === flag){
@@ -2682,26 +2800,28 @@
}
},
/** Expand or collapse node, return Deferred.promise.
*
* @param {EventData} ctx
- * @param {Boolean} [flag=true]
+ * @param {boolean} [flag=true]
+ * @param {object} [opts] additional options. Defaults to {noAnimation: false}
* @returns {$.Promise} The deferred will be resolved as soon as the (lazy)
* data was retrieved, rendered, and the expand animation finshed.
*/
- nodeSetExpanded: function(ctx, flag) {
+ nodeSetExpanded: function(ctx, flag, callOpts) {
+ callOpts = callOpts || {};
var _afterLoad, dfd, i, l, parents, prevAC,
node = ctx.node,
tree = ctx.tree,
- opts = ctx.options;
+ opts = ctx.options,
+ noAnimation = callOpts.noAnimation === true;
+
// flag defaults to true
flag = (flag !== false);
node.debug("nodeSetExpanded(" + flag + ")");
- // TODO: !!node.expanded is nicer, but doesn't pass jshint
- // https://github.com/jshint/jshint/issues/455
-// if( !!node.expanded === !!flag){
+
if((node.expanded && flag) || (!node.expanded && !flag)){
// Nothing to do
node.debug("nodeSetExpanded(" + flag + "): nothing to do");
return _getResolvedPromise(node);
}else if(flag && !node.lazy && !node.hasChildren() ){
@@ -2712,31 +2832,35 @@
return _getRejectedPromise(node, ["locked"]);
}else if ( this._triggerNodeEvent("beforeExpand", node, ctx.originalEvent) === false ){
// Callback returned false
return _getRejectedPromise(node, ["rejected"]);
}
- //
+ // If this node inside a collpased node, no animation and scrolling is needed
+ if( !noAnimation && !node.isVisible() ) {
+ noAnimation = callOpts.noAnimation = true;
+ }
+
dfd = new $.Deferred();
// Auto-collapse mode: collapse all siblings
if( flag && !node.expanded && opts.autoCollapse ) {
parents = node.getParentList(false, true);
prevAC = opts.autoCollapse;
try{
opts.autoCollapse = false;
for(i=0, l=parents.length; i<l; i++){
// TODO: should return promise?
- this._callHook("nodeCollapseSiblings", parents[i]);
+ this._callHook("nodeCollapseSiblings", parents[i], callOpts);
}
}finally{
opts.autoCollapse = prevAC;
}
}
// Trigger expand/collapse after expanding
dfd.done(function(){
ctx.tree._triggerNodeEvent(flag ? "expand" : "collapse", ctx);
- if(opts.autoScroll){
+ if( opts.autoScroll && !noAnimation ) {
// Scroll down to last child, but keep current node visible
node.getLastChild().scrollIntoView(true, node);
}
});
@@ -2763,12 +2887,14 @@
if( node.ul ) {
isVisible = (node.ul.style.display !== "none");
isExpanded = !!node.expanded;
if ( isVisible === isExpanded ) {
node.warn("nodeSetExpanded: UL.style.display already set");
- } else if ( !opts.fx ) {
+
+ } else if ( !opts.fx || noAnimation ) {
node.ul.style.display = ( node.expanded || !parent ) ? "" : "none";
+
} else {
duration = opts.fx.duration || 200;
easing = opts.fx.easing;
node.debug("nodeSetExpanded: animate start...");
$(node.ul).animate(opts.fx, duration, easing, function(){
@@ -2814,11 +2940,11 @@
node.debug("nodeSetExpanded: returns");
return dfd.promise();
},
/**
* @param {EventData} ctx
- * @param {Boolean} [flag=true]
+ * @param {boolean} [flag=true]
*/
nodeSetFocus: function(ctx, flag) {
ctx.node.debug("nodeSetFocus(" + flag + ")");
var ctx2,
tree = ctx.tree,
@@ -2859,11 +2985,11 @@
}
},
/** (De)Select node, return new status (sync).
*
* @param {EventData} ctx
- * @param {Boolean} [flag=true]
+ * @param {boolean} [flag=true]
*/
nodeSetSelected: function(ctx, flag) {
var node = ctx.node,
tree = ctx.tree,
opts = ctx.options;
@@ -3076,125 +3202,37 @@
if( flag !== this.hasFocus() ){
this._hasFocus = flag;
this.$container.toggleClass("fancytree-treefocus", flag);
this._triggerTreeEvent(flag ? "focusTree" : "blurTree");
}
- },
- /** Re-fire beforeActivate and activate events. */
- reactivate: function(setFocus) {
- var node = this.activeNode;
- if( node ) {
- this.activeNode = null; // Force re-activating
- node.setActive();
- if( setFocus ){
- node.setFocus();
- }
- }
- },
- // TODO: redraw()
- /** Reload tree from source and return a promise.
- * @param source
- * @returns {$.Promise}
- */
- reload: function(source) {
- this._callHook("treeClear", this);
- return this._callHook("treeLoad", this, source);
- },
- /**Render tree (i.e. all top-level nodes).
- * @param {Boolean} [force=false]
- * @param {Boolean} [deep=false]
- */
- render: function(force, deep) {
- return this.rootNode.render(force, deep);
- },
- // TODO: selectKey: function(key, select)
- // TODO: serializeArray: function(stopOnParents)
- /**
- * @param {Boolean} [flag=true]
- */
- setFocus: function(flag) {
-// _assert(false, "Not implemented");
- return this._callHook("treeSetFocus", this, flag);
- },
- /**
- * Return all nodes as nested list of {@link NodeData}.
- *
- * @param {Boolean} [includeRoot=false] Returns the hidden system root node (and it's children)
- * @param {function} [callback] Called for every node
- * @returns {Array | object}
- * @see FancytreeNode#toDict
- */
- toDict: function(includeRoot, callback){
- var res = this.rootNode.toDict(true, callback);
- return includeRoot ? res : res.children;
- },
- /**Implicitly called for string conversions.
- * @returns {String}
- */
- toString: function(){
- return "<Fancytree(#" + this._id + ")>";
- },
- /** _trigger a widget event with additional node ctx.
- * @see EventData
- */
- _triggerNodeEvent: function(type, node, originalEvent, extra) {
-// this.debug("_trigger(" + type + "): '" + ctx.node.title + "'", ctx);
- var ctx = this._makeHookContext(node, originalEvent, extra),
- res = this.widget._trigger(type, originalEvent, ctx);
- if(res !== false && ctx.result !== undefined){
- return ctx.result;
- }
- return res;
- },
- /** _trigger a widget event with additional tree data. */
- _triggerTreeEvent: function(type, originalEvent) {
-// this.debug("_trigger(" + type + ")", ctx);
- var ctx = this._makeHookContext(this, originalEvent),
- res = this.widget._trigger(type, originalEvent, ctx);
-
- if(res !== false && ctx.result !== undefined){
- return ctx.result;
- }
- return res;
- },
- /** Call fn(node) for all nodes.
- *
- * @param {function} fn the callback function.
- * Return false to stop iteration, return "skip" to skip this node and children only.
- * @returns {Boolean} false, if the iterator was stopped.
- */
- visit: function(fn) {
- return this.rootNode.visit(fn, false);
- },
- /** Write warning to browser console (prepending tree info)
- *
- * @param {*} msg string or object or array of such
- */
- warn: function(msg){
- Array.prototype.unshift.call(arguments, this.toString());
- consoleApply("warn", arguments);
}
-};
+});
/* ******************************************************************************
* jQuery UI widget boilerplate
- * @ name ui_fancytree
- * @ class The jQuery.ui.fancytree widget
*/
-/* * @namespace ui */
-/* * @namespace ui.fancytree */
-/** @namespace $.ui.fancytree */
+/**
+ * This constructor is not called directly. Use `$(selector).fancytre({})`
+ * to initialize the plugin instead.
+ *
+ * @class ui.fancytree
+ * @classdesc The plugin (derrived from <a href=" http://api.jqueryui.com/jQuery.widget/">jQuery.Widget</a>).<br>
+ * <pre class="sh_javascript sunlight-highlight-javascript">// Access instance methods and members:
+ * var tree = $(selector).fancytree("getTree");
+ * // Access static members:
+ * alert($.ui.fancytree.version);
+ * </pre>
+ */
$.widget("ui.fancytree",
- /** @lends $.ui.fancytree.prototype */
+ /** @lends ui.fancytree# */
{
/**These options will be used as defaults
* @type {FancytreeOptions}
*/
options:
{
- /** @type {Boolean} Make sure, active nodes are visible (expanded). */
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
@@ -3223,10 +3261,11 @@
strings: {
loading: "Loading…",
loadError: "Load error!"
},
tabbable: true,
+ titlesTabbable: false,
_classNames: {
node: "fancytree-node",
folder: "fancytree-folder",
combinedExpanderPrefix: "fancytree-exp-",
combinedIconPrefix: "fancytree-ico-",
@@ -3437,11 +3476,11 @@
/** @returns {FancytreeNode} the active node or null */
getActiveNode: function() {
return this.tree.activeNode;
},
/**
- * @param {String} key
+ * @param {string} key
* @returns {FancytreeNode} the matching node or null
*/
getNodeByKey: function(key) {
return this.tree.getNodeByKey(key);
},
@@ -3456,23 +3495,23 @@
});
// $.ui.fancytree was created by the widget factory. Create a local shortcut:
FT = $.ui.fancytree;
-/**
+/*
* Static members in the `$.ui.fancytree` namespace.
- * @ name $.ui.fancytree
+ *
* @example:
* alert(""version: " + $.ui.fancytree.version);
- * var node = $.ui.fancytree.()
+ * var node = $.ui.fancytree.getNode(element);
*/
$.extend($.ui.fancytree,
- /** @lends $.ui.fancytree */
+ /** @lends ui.fancytree */
{
- /** @type {String} */
- version: "2.0.0-5",
- /** @type {String} */
+ /** @type {string} */
+ version: "2.0.0-6",
+ /** @type {string} */
buildType: "release",
/** @type {int} */
debugLevel: 1, // used by $.ui.fancytree.debug() and as default for tree.options.debugLevel
_nextId: 1,
@@ -3501,11 +3540,11 @@
},
/** Return a {node: FancytreeNode, type: TYPE} object for a mouse event.
*
* @static
* @param {Event} event Mouse event, e.g. click, ...
- * @returns {String} 'title' | 'prefix' | 'expander' | 'checkbox' | 'icon' | undefined
+ * @returns {string} 'title' | 'prefix' | 'expander' | 'checkbox' | 'icon' | undefined
*/
getEventTargetType: function(event){
return this.getEventTarget(event).type;
},
/** Return a {node: FancytreeNode, type: TYPE} object for a mouse event.
@@ -3676,14 +3715,15 @@
});
return children;
},
/** Add Fancytree extension definition to the list of globally available extensions.
*
- * @param name
- * @param definition
+ * @param {Object} definition
*/
- registerExtension: function(name, definition){
- $.ui.fancytree._extensions[name] = definition;
+ registerExtension: function(definition){
+ _assert(definition.name != null, "extensions must have a `name` property.");
+ _assert(definition.version != null, "extensions must have a `version` property.");
+ $.ui.fancytree._extensions[definition.name] = definition;
},
warn: function(msg){
consoleApply("warn", arguments);
}
});