PHP Classes

File: public/flat-admin/js/plugins/dynatree/jquery.dynatree.js

Recommend this page to a friend!
  Classes of adriano123456   Budget System   public/flat-admin/js/plugins/dynatree/jquery.dynatree.js   Download  
File: public/flat-admin/js/plugins/dynatree/jquery.dynatree.js
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: Budget System
Elaborate budgets for purchases of products
Author: By
Last change:
Date: 15 days ago
Size: 105,482 bytes
 

Contents

Class file image Download
/************************************************************************* jquery.dynatree.js Dynamic tree view control, with support for lazy loading of branches. Copyright (c) 2006-2013, Martin Wendt (http://wwWendt.de) Dual licensed under the MIT or GPL Version 2 licenses. http://code.google.com/p/dynatree/wiki/LicenseInfo A current version and some documentation is available at http://dynatree.googlecode.com/ $Version: 1.2.4$ $Revision: 644, 2013-02-12 21:39:36$ @depends: jquery.js @depends: jquery.ui.core.js @depends: jquery.cookie.js *************************************************************************/ /* jsHint options*/ // Note: We currently allow eval() to parse the 'data' attribtes, when initializing from HTML. // TODO: pass jsHint with the options given in grunt.js only. // The following should not be required: /*global alert */ /*jshint nomen:false, smarttabs:true, eqeqeq:false, evil:true, regexp:false */ /************************************************************************* * Debug functions */ var _canLog = true; function _log(mode, msg) { /** * Usage: logMsg("%o was toggled", this); */ if( !_canLog ){ return; } // Remove first argument var args = Array.prototype.slice.apply(arguments, [1]); // Prepend timestamp var dt = new Date(); var tag = dt.getHours()+":"+dt.getMinutes()+":"+dt.getSeconds()+"."+dt.getMilliseconds(); args[0] = tag + " - " + args[0]; try { switch( mode ) { case "info": window.console.info.apply(window.console, args); break; case "warn": window.console.warn.apply(window.console, args); break; default: window.console.log.apply(window.console, args); break; } } catch(e) { if( !window.console ){ _canLog = false; // Permanently disable, when logging is not supported by the browser }else if(e.number === -2146827850){ // fix for IE8, where window.console.log() exists, but does not support .apply() window.console.log(args.join(", ")); } } } /* Check browser version, since $.browser was removed in jQuery 1.9 */ function _checkBrowser(){ var matched, browser; function uaMatch( ua ) { ua = ua.toLowerCase(); var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) || /(webkit)[ \/]([\w.]+)/.exec( ua ) || /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) || /(msie) ([\w.]+)/.exec( ua ) || ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) || []; return { browser: match[ 1 ] || "", version: match[ 2 ] || "0" }; } matched = uaMatch( navigator.userAgent ); browser = {}; if ( matched.browser ) { browser[ matched.browser ] = true; browser.version = matched.version; } if ( browser.chrome ) { browser.webkit = true; } else if ( browser.webkit ) { browser.safari = true; } return browser; } var BROWSER = jQuery.browser || _checkBrowser(); function logMsg(msg) { Array.prototype.unshift.apply(arguments, ["debug"]); _log.apply(this, arguments); } // Forward declaration var getDynaTreePersistData = null; /************************************************************************* * Constants */ var DTNodeStatus_Error = -1; var DTNodeStatus_Loading = 1; var DTNodeStatus_Ok = 0; // Start of local namespace (function($) { /************************************************************************* * Common tool functions. */ var Class = { create: function() { return function() { this.initialize.apply(this, arguments); }; } }; // Tool function to get dtnode from the event target: function getDtNodeFromElement(el) { alert("getDtNodeFromElement is deprecated"); return $.ui.dynatree.getNode(el); /* var iMax = 5; while( el && iMax-- ) { if(el.dtnode) { return el.dtnode; } el = el.parentNode; } return null; */ } function noop() { } /** Compare two dotted version strings (like '10.2.3'). * @returns {Integer} 0: v1 == v2, -1: v1 < v2, 1: v1 > v2 */ function versionCompare(v1, v2) { var v1parts = ("" + v1).split("."), v2parts = ("" + v2).split("."), minLength = Math.min(v1parts.length, v2parts.length), p1, p2, i; // Compare tuple pair-by-pair. for(i = 0; i < minLength; i++) { // Convert to integer if possible, because "8" > "10". p1 = parseInt(v1parts[i], 10); p2 = parseInt(v2parts[i], 10); if (isNaN(p1)){ p1 = v1parts[i]; } if (isNaN(p2)){ p2 = v2parts[i]; } if (p1 == p2) { continue; }else if (p1 > p2) { return 1; }else if (p1 < p2) { return -1; } // one operand is NaN return NaN; } // The longer tuple is always considered 'greater' if (v1parts.length === v2parts.length) { return 0; } return (v1parts.length < v2parts.length) ? -1 : 1; } /************************************************************************* * Class DynaTreeNode */ var DynaTreeNode = Class.create(); DynaTreeNode.prototype = { initialize: function(parent, tree, data) { /** * @constructor */ this.parent = parent; this.tree = tree; if ( typeof data === "string" ){ data = { title: data }; } if( !data.key ){ data.key = "_" + tree._nodeCount++; }else{ data.key = "" + data.key; // issue 371 } this.data = $.extend({}, $.ui.dynatree.nodedatadefaults, data); this.li = null; // not yet created this.span = null; // not yet created this.ul = null; // not yet created this.childList = null; // no subnodes yet this._isLoading = false; // Lazy content is being loaded this.hasSubSel = false; this.bExpanded = false; this.bSelected = false; }, toString: function() { return "DynaTreeNode<" + this.data.key + ">: '" + this.data.title + "'"; }, toDict: function(recursive, callback) { var dict = $.extend({}, this.data); dict.activate = ( this.tree.activeNode === this ); dict.focus = ( this.tree.focusNode === this ); dict.expand = this.bExpanded; dict.select = this.bSelected; if( callback ){ callback(dict); } if( recursive && this.childList ) { dict.children = []; for(var i=0, l=this.childList.length; i<l; i++ ){ dict.children.push(this.childList[i].toDict(true, callback)); } } else { delete dict.children; } return dict; }, fromDict: function(dict) { /** * Update node data. If dict contains 'children', then also replace * the hole sub tree. */ var children = dict.children; if(children === undefined){ this.data = $.extend(this.data, dict); this.render(); return; } dict = $.extend({}, dict); dict.children = undefined; this.data = $.extend(this.data, dict); this.removeChildren(); this.addChild(children); }, _getInnerHtml: function() { var tree = this.tree, opts = tree.options, cache = tree.cache, level = this.getLevel(), data = this.data, res = "", imageSrc; // connector (expanded, expandable or simple) if( level < opts.minExpandLevel ) { if(level > 1){ res += cache.tagConnector; } // .. else (i.e. for root level) skip expander/connector altogether } else if( this.hasChildren() !== false ) { res += cache.tagExpander; } else { res += cache.tagConnector; } // Checkbox mode if( opts.checkbox && data.hideCheckbox !== true && !data.isStatusNode ) { res += cache.tagCheckbox; } // folder or doctype icon if ( data.icon ) { if (data.icon.charAt(0) === "/"){ imageSrc = data.icon; }else{ imageSrc = opts.imagePath + data.icon; } res += "<img src='" + imageSrc + "' alt='' />"; } else if ( data.icon === false ) { // icon == false means 'no icon' // noop(); // keep JSLint happy } else if ( data.iconClass ) { res += "<span class='" + " " + data.iconClass + "'></span>"; } else { // icon == null means 'default icon' res += cache.tagNodeIcon; } // node title var nodeTitle = ""; if ( opts.onCustomRender ){ nodeTitle = opts.onCustomRender.call(tree, this) || ""; } if(!nodeTitle){ var tooltip = data.tooltip ? ' title="' + data.tooltip.replace(/\"/g, '&quot;') + '"' : '', href = data.href || "#"; if( opts.noLink || data.noLink ) { nodeTitle = '<span style="display:inline-block;" class="' + opts.classNames.title + '"' + tooltip + '>' + data.title + '</span>'; // this.tree.logDebug("nodeTitle: " + nodeTitle); } else { nodeTitle = '<a href="' + href + '" class="' + opts.classNames.title + '"' + tooltip + '>' + data.title + '</a>'; } } res += nodeTitle; return res; }, _fixOrder: function() { /** * Make sure, that <li> order matches childList order. */ var cl = this.childList; if( !cl || !this.ul ){ return; } var childLI = this.ul.firstChild; for(var i=0, l=cl.length-1; i<l; i++) { var childNode1 = cl[i]; var childNode2 = childLI.dtnode; if( childNode1 !== childNode2 ) { this.tree.logDebug("_fixOrder: mismatch at index " + i + ": " + childNode1 + " != " + childNode2); this.ul.insertBefore(childNode1.li, childNode2.li); } else { childLI = childLI.nextSibling; } } }, render: function(useEffects, includeInvisible) { /** * Create <li><span>..</span> .. </li> tags for this node. * * <li id='KEY' dtnode=NODE> // This div contains the node's span and list of child div's. * <span class='title'>S S S A</span> // Span contains graphic spans and title <a> tag * <ul> // only present, when node has children * <li id='KEY' dtnode=NODE>child1</li> * <li id='KEY' dtnode=NODE>child2</li> * </ul> * </li> */ // this.tree.logDebug("%s.render(%s)", this, useEffects); // --- var tree = this.tree, parent = this.parent, data = this.data, opts = tree.options, cn = opts.classNames, isLastSib = this.isLastSibling(), firstTime = false; if( !parent && !this.ul ) { // Root node has only a <ul> this.li = this.span = null; this.ul = document.createElement("ul"); if( opts.minExpandLevel > 1 ){ this.ul.className = cn.container + " " + cn.noConnector; }else{ this.ul.className = cn.container; } } else if( parent ) { // Create <li><span /> </li> if( ! this.li ) { firstTime = true; this.li = document.createElement("li"); this.li.dtnode = this; if( data.key && opts.generateIds ){ this.li.id = opts.idPrefix + data.key; } this.span = document.createElement("span"); this.span.className = cn.title; this.li.appendChild(this.span); if( !parent.ul ) { // This is the parent's first child: create UL tag // (Hidden, because it will be parent.ul = document.createElement("ul"); parent.ul.style.display = "none"; parent.li.appendChild(parent.ul); // if( opts.minExpandLevel > this.getLevel() ){ // parent.ul.className = cn.noConnector; // } } // set node connector images, links and text // this.span.innerHTML = this._getInnerHtml(); parent.ul.appendChild(this.li); } // set node connector images, links and text this.span.innerHTML = this._getInnerHtml(); // Set classes for current status var cnList = []; cnList.push(cn.node); if( data.isFolder ){ cnList.push(cn.folder); } if( this.bExpanded ){ cnList.push(cn.expanded); } if( this.hasChildren() !== false ){ cnList.push(cn.hasChildren); } if( data.isLazy && this.childList === null ){ cnList.push(cn.lazy); } if( isLastSib ){ cnList.push(cn.lastsib); } if( this.bSelected ){ cnList.push(cn.selected); } if( this.hasSubSel ){ cnList.push(cn.partsel); } if( tree.activeNode === this ){ cnList.push(cn.active); } if( data.addClass ){ cnList.push(data.addClass); } // IE6 doesn't correctly evaluate multiple class names, // so we create combined class names that can be used in the CSS cnList.push(cn.combinedExpanderPrefix + (this.bExpanded ? "e" : "c") + (data.isLazy && this.childList === null ? "d" : "") + (isLastSib ? "l" : "") ); cnList.push(cn.combinedIconPrefix + (this.bExpanded ? "e" : "c") + (data.isFolder ? "f" : "") ); this.span.className = cnList.join(" "); // TODO: we should not set this in the <span> tag also, if we set it here: this.li.className = isLastSib ? cn.lastsib : ""; // Allow tweaking, binding, after node was created for the first time if(firstTime && opts.onCreate){ opts.onCreate.call(tree, this, this.span); } // Hide children, if node is collapsed // this.ul.style.display = ( this.bExpanded || !parent ) ? "" : "none"; // Allow tweaking after node state was rendered if(opts.onRender){ opts.onRender.call(tree, this, this.span); } } // Visit child nodes if( (this.bExpanded || includeInvisible === true) && this.childList ) { for(var i=0, l=this.childList.length; i<l; i++) { this.childList[i].render(false, includeInvisible); } // Make sure the tag order matches the child array this._fixOrder(); } // Hide children, if node is collapsed if( this.ul ) { var isHidden = (this.ul.style.display === "none"); var isExpanded = !!this.bExpanded; // logMsg("isHidden:%s", isHidden); if( useEffects && opts.fx && (isHidden === isExpanded) ) { var duration = opts.fx.duration || 200; $(this.ul).animate(opts.fx, duration); } else { this.ul.style.display = ( this.bExpanded || !parent ) ? "" : "none"; } } }, /** Return '/id1/id2/id3'. */ getKeyPath: function(excludeSelf) { var path = []; this.visitParents(function(node){ if(node.parent){ path.unshift(node.data.key); } }, !excludeSelf); return "/" + path.join(this.tree.options.keyPathSeparator); }, getParent: function() { return this.parent; }, getChildren: function() { if(this.hasChildren() === undefined){ return undefined; // Lazy node: unloaded, currently loading, or load error } return this.childList; }, /** Check if node has children (returns undefined, if not sure). */ hasChildren: function() { if(this.data.isLazy){ if(this.childList === null || this.childList === undefined){ // Not yet loaded return undefined; }else if(this.childList.length === 0){ // Loaded, but response was empty return false; }else if(this.childList.length === 1 && this.childList[0].isStatusNode()){ // Currently loading or load error return undefined; } return true; } return !!this.childList; }, isFirstSibling: function() { var p = this.parent; return !p || p.childList[0] === this; }, isLastSibling: function() { var p = this.parent; return !p || p.childList[p.childList.length-1] === this; }, isLoading: function() { return !!this._isLoading; }, getPrevSibling: function() { if( !this.parent ){ return null; } var ac = this.parent.childList; for(var i=1, l=ac.length; i<l; i++){ // start with 1, so prev(first) = null if( ac[i] === this ){ return ac[i-1]; } } return null; }, getNextSibling: function() { if( !this.parent ){ return null; } var ac = this.parent.childList; for(var i=0, l=ac.length-1; i<l; i++){ // up to length-2, so next(last) = null if( ac[i] === this ){ return ac[i+1]; } } return null; }, isStatusNode: function() { return (this.data.isStatusNode === true); }, isChildOf: function(otherNode) { return (this.parent && this.parent === otherNode); }, isDescendantOf: function(otherNode) { if(!otherNode){ return false; } var p = this.parent; while( p ) { if( p === otherNode ){ return true; } p = p.parent; } return false; }, countChildren: function() { var cl = this.childList; if( !cl ){ return 0; } var n = cl.length; for(var i=0, l=n; i<l; i++){ var child = cl[i]; n += child.countChildren(); } return n; }, /**Sort child list by title. * cmd: optional compare function. * deep: optional: pass true to sort all descendant nodes. */ sortChildren: function(cmp, deep) { var cl = this.childList; if( !cl ){ return; } cmp = cmp || function(a, b) { // return a.data.title === b.data.title ? 0 : a.data.title > b.data.title ? 1 : -1; var x = a.data.title.toLowerCase(), y = b.data.title.toLowerCase(); return x === y ? 0 : x > y ? 1 : -1; }; cl.sort(cmp); if( deep ){ for(var i=0, l=cl.length; i<l; i++){ if( cl[i].childList ){ cl[i].sortChildren(cmp, "$norender$"); } } } if( deep !== "$norender$" ){ this.render(); } }, _setStatusNode: function(data) { // Create, modify or remove the status child node (pass 'null', to remove it). var firstChild = ( this.childList ? this.childList[0] : null ); if( !data ) { if ( firstChild && firstChild.isStatusNode()) { try{ // I've seen exceptions here with loadKeyPath... if(this.ul){ this.ul.removeChild(firstChild.li); firstChild.li = null; // avoid leaks (issue 215) } }catch(e){} if( this.childList.length === 1 ){ this.childList = []; }else{ this.childList.shift(); } } } else if ( firstChild ) { data.isStatusNode = true; data.key = "_statusNode"; firstChild.data = data; firstChild.render(); } else { data.isStatusNode = true; data.key = "_statusNode"; firstChild = this.addChild(data); } }, setLazyNodeStatus: function(lts, opts) { var tooltip = (opts && opts.tooltip) ? opts.tooltip : null, info = (opts && opts.info) ? " (" + opts.info + ")" : ""; switch( lts ) { case DTNodeStatus_Ok: this._setStatusNode(null); $(this.span).removeClass(this.tree.options.classNames.nodeLoading); this._isLoading = false; // this.render(); if( this.tree.options.autoFocus ) { if( this === this.tree.tnRoot && this.childList && this.childList.length > 0) { // special case: using ajaxInit this.childList[0].focus(); } else { this.focus(); } } break; case DTNodeStatus_Loading: this._isLoading = true; $(this.span).addClass(this.tree.options.classNames.nodeLoading); // The root is hidden, so we set a temporary status child if(!this.parent){ this._setStatusNode({ title: this.tree.options.strings.loading + info, tooltip: tooltip, addClass: this.tree.options.classNames.nodeWait }); } break; case DTNodeStatus_Error: this._isLoading = false; // $(this.span).addClass(this.tree.options.classNames.nodeError); this._setStatusNode({ title: this.tree.options.strings.loadError + info, tooltip: tooltip, addClass: this.tree.options.classNames.nodeError }); break; default: throw "Bad LazyNodeStatus: '" + lts + "'."; } }, _parentList: function(includeRoot, includeSelf) { var l = []; var dtn = includeSelf ? this : this.parent; while( dtn ) { if( includeRoot || dtn.parent ){ l.unshift(dtn); } dtn = dtn.parent; } return l; }, getLevel: function() { /** * Return node depth. 0: System root node, 1: visible top-level node. */ var level = 0; var dtn = this.parent; while( dtn ) { level++; dtn = dtn.parent; } return level; }, _getTypeForOuterNodeEvent: function(event) { /** Return the inner node span (title, checkbox or expander) if * event.target points to the outer span. * This function should fix issue #93: * FF2 ignores empty spans, when generating events (returning the parent instead). */ var cns = this.tree.options.classNames; var target = event.target; // Only process clicks on an outer node span (probably due to a FF2 event handling bug) if( target.className.indexOf(cns.node) < 0 ) { return null; } // Event coordinates, relative to outer node span: var eventX = event.pageX - target.offsetLeft; var eventY = event.pageY - target.offsetTop; for(var i=0, l=target.childNodes.length; i<l; i++) { var cn = target.childNodes[i]; var x = cn.offsetLeft - target.offsetLeft; var y = cn.offsetTop - target.offsetTop; var nx = cn.clientWidth, ny = cn.clientHeight; // alert (cn.className + ": " + x + ", " + y + ", s:" + nx + ", " + ny); if( eventX >= x && eventX <= (x+nx) && eventY >= y && eventY <= (y+ny) ) { // alert("HIT "+ cn.className); if( cn.className==cns.title ){ return "title"; }else if( cn.className==cns.expander ){ return "expander"; }else if( cn.className==cns.checkbox ){ return "checkbox"; }else if( cn.className==cns.nodeIcon ){ return "icon"; } } } return "prefix"; }, getEventTargetType: function(event) { // Return the part of a node, that a click event occured on. // Note: there is no check, if the event was fired on THIS node. var tcn = event && event.target ? event.target.className : "", cns = this.tree.options.classNames; if( tcn === cns.title ){ return "title"; }else if( tcn === cns.expander ){ return "expander"; }else if( tcn === cns.checkbox ){ return "checkbox"; }else if( tcn === cns.nodeIcon ){ return "icon"; }else if( tcn === cns.empty || tcn === cns.vline || tcn === cns.connector ){ return "prefix"; }else if( tcn.indexOf(cns.node) >= 0 ){ // FIX issue #93 return this._getTypeForOuterNodeEvent(event); } return null; }, isVisible: function() { // Return true, if all parents are expanded. var parents = this._parentList(true, false); for(var i=0, l=parents.length; i<l; i++){ if( ! parents[i].bExpanded ){ return false; } } return true; }, makeVisible: function() { // Make sure, all parents are expanded var parents = this._parentList(true, false); for(var i=0, l=parents.length; i<l; i++){ parents[i]._expand(true); } }, focus: function() { // TODO: check, if we already have focus // this.tree.logDebug("dtnode.focus(): %o", this); this.makeVisible(); try { $(this.span).find(">a").focus(); } catch(e) { } }, isFocused: function() { return (this.tree.tnFocused === this); }, _activate: function(flag, fireEvents) { // (De)Activate - but not focus - this node. this.tree.logDebug("dtnode._activate(%o, fireEvents=%o) - %o", flag, fireEvents, this); var opts = this.tree.options; if( this.data.isStatusNode ){ return; } if ( fireEvents && opts.onQueryActivate && opts.onQueryActivate.call(this.tree, flag, this) === false ){ return; // Callback returned false } if( flag ) { // Activate if( this.tree.activeNode ) { if( this.tree.activeNode === this ){ return; } this.tree.activeNode.deactivate(); } if( opts.activeVisible ){ this.makeVisible(); } this.tree.activeNode = this; if( opts.persist ){ $.cookie(opts.cookieId+"-active", this.data.key, opts.cookie); } this.tree.persistence.activeKey = this.data.key; $(this.span).addClass(opts.classNames.active); if ( fireEvents && opts.onActivate ){ opts.onActivate.call(this.tree, this); } } else { // Deactivate if( this.tree.activeNode === this ) { if ( opts.onQueryActivate && opts.onQueryActivate.call(this.tree, false, this) === false ){ return; // Callback returned false } $(this.span).removeClass(opts.classNames.active); if( opts.persist ) { // Note: we don't pass null, but ''. So the cookie is not deleted. // If we pass null, we also have to pass a COPY of opts, because $cookie will override opts.expires (issue 84) $.cookie(opts.cookieId+"-active", "", opts.cookie); } this.tree.persistence.activeKey = null; this.tree.activeNode = null; if ( fireEvents && opts.onDeactivate ){ opts.onDeactivate.call(this.tree, this); } } } }, activate: function() { // Select - but not focus - this node. // this.tree.logDebug("dtnode.activate(): %o", this); this._activate(true, true); }, activateSilently: function() { this._activate(true, false); }, deactivate: function() { // this.tree.logDebug("dtnode.deactivate(): %o", this); this._activate(false, true); }, isActive: function() { return (this.tree.activeNode === this); }, _userActivate: function() { // Handle user click / [space] / [enter], according to clickFolderMode. var activate = true; var expand = false; if ( this.data.isFolder ) { switch( this.tree.options.clickFolderMode ) { case 2: activate = false; expand = true; break; case 3: activate = expand = true; break; } } if( this.parent === null ) { expand = false; } if( expand ) { this.toggleExpand(); this.focus(); } if( activate ) { this.activate(); } }, _setSubSel: function(hasSubSel) { if( hasSubSel ) { this.hasSubSel = true; $(this.span).addClass(this.tree.options.classNames.partsel); } else { this.hasSubSel = false; $(this.span).removeClass(this.tree.options.classNames.partsel); } }, /** * Fix selection and partsel status, of parent nodes, according to current status of * end nodes. */ _updatePartSelectionState: function() { // alert("_updatePartSelectionState " + this); // this.tree.logDebug("_updatePartSelectionState() - %o", this); var sel; // Return `true` or `false` for end nodes and remove part-sel flag if( ! this.hasChildren() ){ sel = (this.bSelected && !this.data.unselectable && !this.data.isStatusNode); this._setSubSel(false); return sel; } // Return `true`, `false`, or `undefined` for parent nodes var i, l, cl = this.childList, allSelected = true, allDeselected = true; for(i=0, l=cl.length; i<l; i++) { var n = cl[i], s = n._updatePartSelectionState(); if( s !== false){ allDeselected = false; } if( s !== true){ allSelected = false; } } if( allSelected ){ sel = true; } else if ( allDeselected ){ sel = false; } else { sel = undefined; } this._setSubSel(sel === undefined); this.bSelected = (sel === true); return sel; }, /** * Fix selection status, after this node was (de)selected in multi-hier mode. * This includes (de)selecting all children. */ _fixSelectionState: function() { // alert("_fixSelectionState " + this); // this.tree.logDebug("_fixSelectionState(%s) - %o", this.bSelected, this); var p, i, l; if( this.bSelected ) { // Select all children this.visit(function(node){ node.parent._setSubSel(true); if(!node.data.unselectable){ node._select(true, false, false); } }); // Select parents, if all children are selected p = this.parent; while( p ) { p._setSubSel(true); var allChildsSelected = true; for(i=0, l=p.childList.length; i<l; i++) { var n = p.childList[i]; if( !n.bSelected && !n.data.isStatusNode && !n.data.unselectable) { // issue 305 proposes this: // if( !n.bSelected && !n.data.isStatusNode ) { allChildsSelected = false; break; } } if( allChildsSelected ){ p._select(true, false, false); } p = p.parent; } } else { // Deselect all children this._setSubSel(false); this.visit(function(node){ node._setSubSel(false); node._select(false, false, false); }); // Deselect parents, and recalc hasSubSel p = this.parent; while( p ) { p._select(false, false, false); var isPartSel = false; for(i=0, l=p.childList.length; i<l; i++) { if( p.childList[i].bSelected || p.childList[i].hasSubSel ) { isPartSel = true; break; } } p._setSubSel(isPartSel); p = p.parent; } } }, _select: function(sel, fireEvents, deep) { // Select - but not focus - this node. // this.tree.logDebug("dtnode._select(%o) - %o", sel, this); var opts = this.tree.options; if( this.data.isStatusNode ){ return; } // if( this.bSelected === sel ) { // this.tree.logDebug("dtnode._select(%o) IGNORED - %o", sel, this); return; } // Allow event listener to abort selection if ( fireEvents && opts.onQuerySelect && opts.onQuerySelect.call(this.tree, sel, this) === false ){ return; // Callback returned false } // Force single-selection if( opts.selectMode==1 && sel ) { this.tree.visit(function(node){ if( node.bSelected ) { // Deselect; assuming that in selectMode:1 there's max. one other selected node node._select(false, false, false); return false; } }); } this.bSelected = sel; // this.tree._changeNodeList("select", this, sel); if( sel ) { if( opts.persist ){ this.tree.persistence.addSelect(this.data.key); } $(this.span).addClass(opts.classNames.selected); if( deep && opts.selectMode === 3 ){ this._fixSelectionState(); } if ( fireEvents && opts.onSelect ){ opts.onSelect.call(this.tree, true, this); } } else { if( opts.persist ){ this.tree.persistence.clearSelect(this.data.key); } $(this.span).removeClass(opts.classNames.selected); if( deep && opts.selectMode === 3 ){ this._fixSelectionState(); } if ( fireEvents && opts.onSelect ){ opts.onSelect.call(this.tree, false, this); } } }, select: function(sel) { // Select - but not focus - this node. // this.tree.logDebug("dtnode.select(%o) - %o", sel, this); if( this.data.unselectable ){ return this.bSelected; } return this._select(sel!==false, true, true); }, toggleSelect: function() { // this.tree.logDebug("dtnode.toggleSelect() - %o", this); return this.select(!this.bSelected); }, isSelected: function() { return this.bSelected; }, isLazy: function() { return !!this.data.isLazy; }, _loadContent: function() { try { var opts = this.tree.options; this.tree.logDebug("_loadContent: start - %o", this); this.setLazyNodeStatus(DTNodeStatus_Loading); if( true === opts.onLazyRead.call(this.tree, this) ) { // If function returns 'true', we assume that the loading is done: this.setLazyNodeStatus(DTNodeStatus_Ok); // Otherwise (i.e. if the loading was started as an asynchronous process) // the onLazyRead(dtnode) handler is expected to call dtnode.setLazyNodeStatus(DTNodeStatus_Ok/_Error) when done. this.tree.logDebug("_loadContent: succeeded - %o", this); } } catch(e) { this.tree.logWarning("_loadContent: failed - %o", e); this.setLazyNodeStatus(DTNodeStatus_Error, {tooltip: ""+e}); } }, _expand: function(bExpand, forceSync) { if( this.bExpanded === bExpand ) { this.tree.logDebug("dtnode._expand(%o) IGNORED - %o", bExpand, this); return; } this.tree.logDebug("dtnode._expand(%o) - %o", bExpand, this); var opts = this.tree.options; if( !bExpand && this.getLevel() < opts.minExpandLevel ) { this.tree.logDebug("dtnode._expand(%o) prevented collapse - %o", bExpand, this); return; } if ( opts.onQueryExpand && opts.onQueryExpand.call(this.tree, bExpand, this) === false ){ return; // Callback returned false } this.bExpanded = bExpand; // Persist expand state if( opts.persist ) { if( bExpand ){ this.tree.persistence.addExpand(this.data.key); }else{ this.tree.persistence.clearExpand(this.data.key); } } // Do not apply animations in init phase, or before lazy-loading var allowEffects = !(this.data.isLazy && this.childList === null) && !this._isLoading && !forceSync; this.render(allowEffects); // Auto-collapse mode: collapse all siblings if( this.bExpanded && this.parent && opts.autoCollapse ) { var parents = this._parentList(false, true); for(var i=0, l=parents.length; i<l; i++){ parents[i].collapseSiblings(); } } // If the currently active node is now hidden, deactivate it if( opts.activeVisible && this.tree.activeNode && ! this.tree.activeNode.isVisible() ) { this.tree.activeNode.deactivate(); } // Expanding a lazy node: set 'loading...' and call callback if( bExpand && this.data.isLazy && this.childList === null && !this._isLoading ) { this._loadContent(); return; } if ( opts.onExpand ){ opts.onExpand.call(this.tree, bExpand, this); } }, isExpanded: function() { return this.bExpanded; }, expand: function(flag) { flag = (flag !== false); if( !this.childList && !this.data.isLazy && flag ){ return; // Prevent expanding empty nodes } else if( this.parent === null && !flag ){ return; // Prevent collapsing the root } this._expand(flag); }, scheduleAction: function(mode, ms) { /** Schedule activity for delayed execution (cancel any pending request). * scheduleAction('cancel') will cancel the request. */ if( this.tree.timer ) { clearTimeout(this.tree.timer); this.tree.logDebug("clearTimeout(%o)", this.tree.timer); } var self = this; // required for closures switch (mode) { case "cancel": // Simply made sure that timer was cleared break; case "expand": this.tree.timer = setTimeout(function(){ self.tree.logDebug("setTimeout: trigger expand"); self.expand(true); }, ms); break; case "activate": this.tree.timer = setTimeout(function(){ self.tree.logDebug("setTimeout: trigger activate"); self.activate(); }, ms); break; default: throw "Invalid mode " + mode; } this.tree.logDebug("setTimeout(%s, %s): %s", mode, ms, this.tree.timer); }, toggleExpand: function() { this.expand(!this.bExpanded); }, collapseSiblings: function() { if( this.parent === null ){ return; } var ac = this.parent.childList; for (var i=0, l=ac.length; i<l; i++) { if ( ac[i] !== this && ac[i].bExpanded ){ ac[i]._expand(false); } } }, _onClick: function(event) { // this.tree.logDebug("dtnode.onClick(" + event.type + "): dtnode:" + this + ", button:" + event.button + ", which: " + event.which); var targetType = this.getEventTargetType(event); if( targetType === "expander" ) { // Clicking the expander icon always expands/collapses this.toggleExpand(); this.focus(); // issue 95 } else if( targetType === "checkbox" ) { // Clicking the checkbox always (de)selects this.toggleSelect(); this.focus(); // issue 95 } else { this._userActivate(); var aTag = this.span.getElementsByTagName("a"); if(aTag[0]){ // issue 154, 313 // if(!($.browser.msie && parseInt($.browser.version, 10) < 9)){ if(!(BROWSER.msie && parseInt(BROWSER.version, 10) < 9)){ aTag[0].focus(); } }else{ // 'noLink' option was set return true; } } // Make sure that clicks stop, otherwise <a href='#'> jumps to the top event.preventDefault(); }, _onDblClick: function(event) { // this.tree.logDebug("dtnode.onDblClick(" + event.type + "): dtnode:" + this + ", button:" + event.button + ", which: " + event.which); }, _onKeydown: function(event) { // this.tree.logDebug("dtnode.onKeydown(" + event.type + "): dtnode:" + this + ", charCode:" + event.charCode + ", keyCode: " + event.keyCode + ", which: " + event.which); var handled = true, sib; // alert("keyDown" + event.which); switch( event.which ) { // charCodes: // case 43: // '+' case 107: // '+' case 187: // '+' @ Chrome, Safari if( !this.bExpanded ){ this.toggleExpand(); } break; // case 45: // '-' case 109: // '-' case 189: // '+' @ Chrome, Safari if( this.bExpanded ){ this.toggleExpand(); } break; //~ case 42: // '*' //~ break; //~ case 47: // '/' //~ break; // case 13: // <enter> // <enter> on a focused <a> tag seems to generate a click-event. // this._userActivate(); // break; case 32: // <space> this._userActivate(); break; case 8: // <backspace> if( this.parent ){ this.parent.focus(); } break; case 37: // <left> if( this.bExpanded ) { this.toggleExpand(); this.focus(); // } else if( this.parent && (this.tree.options.rootVisible || this.parent.parent) ) { } else if( this.parent && this.parent.parent ) { this.parent.focus(); } break; case 39: // <right> if( !this.bExpanded && (this.childList || this.data.isLazy) ) { this.toggleExpand(); this.focus(); } else if( this.childList ) { this.childList[0].focus(); } break; case 38: // <up> sib = this.getPrevSibling(); while( sib && sib.bExpanded && sib.childList ){ sib = sib.childList[sib.childList.length-1]; } // if( !sib && this.parent && (this.tree.options.rootVisible || this.parent.parent) ) if( !sib && this.parent && this.parent.parent ){ sib = this.parent; } if( sib ){ sib.focus(); } break; case 40: // <down> if( this.bExpanded && this.childList ) { sib = this.childList[0]; } else { var parents = this._parentList(false, true); for(var i=parents.length-1; i>=0; i--) { sib = parents[i].getNextSibling(); if( sib ){ break; } } } if( sib ){ sib.focus(); } break; default: handled = false; } // Return false, if handled, to prevent default processing // return !handled; if(handled){ event.preventDefault(); } }, _onKeypress: function(event) { // onKeypress is only hooked to allow user callbacks. // We don't process it, because IE and Safari don't fire keypress for cursor keys. // this.tree.logDebug("dtnode.onKeypress(" + event.type + "): dtnode:" + this + ", charCode:" + event.charCode + ", keyCode: " + event.keyCode + ", which: " + event.which); }, _onFocus: function(event) { // Handles blur and focus events. // this.tree.logDebug("dtnode._onFocus(%o): %o", event, this); var opts = this.tree.options; if ( event.type == "blur" || event.type == "focusout" ) { if ( opts.onBlur ){ opts.onBlur.call(this.tree, this); } if( this.tree.tnFocused ){ $(this.tree.tnFocused.span).removeClass(opts.classNames.focused); } this.tree.tnFocused = null; if( opts.persist ){ $.cookie(opts.cookieId+"-focus", "", opts.cookie); } } else if ( event.type=="focus" || event.type=="focusin") { // Fix: sometimes the blur event is not generated if( this.tree.tnFocused && this.tree.tnFocused !== this ) { this.tree.logDebug("dtnode.onFocus: out of sync: curFocus: %o", this.tree.tnFocused); $(this.tree.tnFocused.span).removeClass(opts.classNames.focused); } this.tree.tnFocused = this; if ( opts.onFocus ){ opts.onFocus.call(this.tree, this); } $(this.tree.tnFocused.span).addClass(opts.classNames.focused); if( opts.persist ){ $.cookie(opts.cookieId+"-focus", this.data.key, opts.cookie); } } // TODO: return anything? // return false; }, visit: function(fn, includeSelf) { // Call fn(node) for all child nodes. Stop iteration, if fn() returns false. var res = true; if( includeSelf === true ) { res = fn(this); if( res === false || res == "skip" ){ return res; } } if(this.childList){ for(var i=0, l=this.childList.length; i<l; i++){ res = this.childList[i].visit(fn, true); if( res === false ){ break; } } } return res; }, visitParents: function(fn, includeSelf) { // Visit parent nodes (bottom up) if(includeSelf && fn(this) === false){ return false; } var p = this.parent; while( p ) { if(fn(p) === false){ return false; } p = p.parent; } return true; }, remove: function() { // Remove this node // this.tree.logDebug ("%s.remove()", this); if ( this === this.tree.root ){ throw "Cannot remove system root"; } return this.parent.removeChild(this); }, removeChild: function(tn) { // Remove tn from list of direct children. var ac = this.childList; if( ac.length == 1 ) { if( tn !== ac[0] ){ throw "removeChild: invalid child"; } return this.removeChildren(); } if( tn === this.tree.activeNode ){ tn.deactivate(); } if( this.tree.options.persist ) { if( tn.bSelected ){ this.tree.persistence.clearSelect(tn.data.key); } if ( tn.bExpanded ){ this.tree.persistence.clearExpand(tn.data.key); } } tn.removeChildren(true); if(this.ul){ // $("li", $(this.ul)).remove(); // issue 399 this.ul.removeChild(tn.li); // issue 402 } for(var i=0, l=ac.length; i<l; i++) { if( ac[i] === tn ) { this.childList.splice(i, 1); // delete tn; // JSLint complained break; } } }, removeChildren: function(isRecursiveCall, retainPersistence) { // Remove all child nodes (more efficiently than recursive remove()) this.tree.logDebug("%s.removeChildren(%o)", this, isRecursiveCall); var tree = this.tree; var ac = this.childList; if( ac ) { for(var i=0, l=ac.length; i<l; i++) { var tn = ac[i]; if ( tn === tree.activeNode && !retainPersistence ){ tn.deactivate(); } if( this.tree.options.persist && !retainPersistence ) { if( tn.bSelected ){ this.tree.persistence.clearSelect(tn.data.key); } if ( tn.bExpanded ){ this.tree.persistence.clearExpand(tn.data.key); } } tn.removeChildren(true, retainPersistence); if(this.ul){ // this.ul.removeChild(tn.li); $("li", $(this.ul)).remove(); // issue 231 } // delete tn; JSLint complained } // Set to 'null' which is interpreted as 'not yet loaded' for lazy // nodes this.childList = null; } if( ! isRecursiveCall ) { // this._expand(false); // this.isRead = false; this._isLoading = false; this.render(); } }, setTitle: function(title) { this.fromDict({title: title}); }, reload: function(force) { throw "Use reloadChildren() instead"; }, reloadChildren: function(callback) { // Reload lazy content (expansion state is maintained). if( this.parent === null ){ throw "Use tree.reload() instead"; }else if( ! this.data.isLazy ){ throw "node.reloadChildren() requires lazy nodes."; } // appendAjax triggers 'nodeLoaded' event. // We listen to this, if a callback was passed to reloadChildren if(callback){ var self = this; var eventType = "nodeLoaded.dynatree." + this.tree.$tree.attr("id") + "." + this.data.key; this.tree.$tree.bind(eventType, function(e, node, isOk){ self.tree.$tree.unbind(eventType); self.tree.logDebug("loaded %o, %o, %o", e, node, isOk); if(node !== self){ throw "got invalid load event"; } callback.call(self.tree, node, isOk); }); } // The expansion state is maintained this.removeChildren(); this._loadContent(); // if( this.bExpanded ) { // // Remove children first, to prevent effects being applied // this.removeChildren(); // // then force re-expand to trigger lazy loading //// this.expand(false); //// this.expand(true); // this._loadContent(); // } else { // this.removeChildren(); // this._loadContent(); // } }, /** * Make sure the node with a given key path is available in the tree. */ _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(); if(this.childList){ 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; // Note: this line gives a JSLint warning (Don't make functions within a loop) /*jshint loopfunc:true */ 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"); } }); // 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 // Callback params: child: undefined, the segment, isEndNode (segList.length === 0) callback.call(tree, undefined, "notfound", seg, segList.length === 0); tree.logWarning("Node not found: " + seg); return; }, resetLazy: function() { // Discard lazy content. if( this.parent === null ){ throw "Use tree.reload() instead"; }else if( ! this.data.isLazy ){ throw "node.resetLazy() requires lazy nodes."; } this.expand(false); this.removeChildren(); }, _addChildNode: function(dtnode, beforeNode) { /** * Internal function to add one single DynatreeNode as a child. * */ var tree = this.tree, opts = tree.options, pers = tree.persistence; // tree.logDebug("%s._addChildNode(%o)", this, dtnode); // --- Update and fix dtnode attributes if necessary dtnode.parent = this; // if( beforeNode && (beforeNode.parent !== this || beforeNode === dtnode ) ) // throw "<beforeNode> must be another child of <this>"; // --- Add dtnode as a child if ( this.childList === null ) { this.childList = []; } else if( ! beforeNode ) { // Fix 'lastsib' if(this.childList.length > 0) { $(this.childList[this.childList.length-1].span).removeClass(opts.classNames.lastsib); } } if( beforeNode ) { var iBefore = $.inArray(beforeNode, this.childList); if( iBefore < 0 ){ throw "<beforeNode> must be a child of <this>"; } this.childList.splice(iBefore, 0, dtnode); } else { // Append node this.childList.push(dtnode); } // --- Handle persistence // Initial status is read from cookies, if persistence is active and // cookies are already present. // Otherwise the status is read from the data attributes and then persisted. var isInitializing = tree.isInitializing(); if( opts.persist && pers.cookiesFound && isInitializing ) { // Init status from cookies // tree.logDebug("init from cookie, pa=%o, dk=%o", pers.activeKey, dtnode.data.key); if( pers.activeKey === dtnode.data.key ){ tree.activeNode = dtnode; } if( pers.focusedKey === dtnode.data.key ){ tree.focusNode = dtnode; } dtnode.bExpanded = ($.inArray(dtnode.data.key, pers.expandedKeyList) >= 0); dtnode.bSelected = ($.inArray(dtnode.data.key, pers.selectedKeyList) >= 0); // tree.logDebug(" key=%o, bSelected=%o", dtnode.data.key, dtnode.bSelected); } else { // Init status from data (Note: we write the cookies after the init phase) // tree.logDebug("init from data"); if( dtnode.data.activate ) { tree.activeNode = dtnode; if( opts.persist ){ pers.activeKey = dtnode.data.key; } } if( dtnode.data.focus ) { tree.focusNode = dtnode; if( opts.persist ){ pers.focusedKey = dtnode.data.key; } } dtnode.bExpanded = ( dtnode.data.expand === true ); // Collapsed by default if( dtnode.bExpanded && opts.persist ){ pers.addExpand(dtnode.data.key); } dtnode.bSelected = ( dtnode.data.select === true ); // Deselected by default /* Doesn't work, cause pers.selectedKeyList may be null if( dtnode.bSelected && opts.selectMode==1 && pers.selectedKeyList && pers.selectedKeyList.length>0 ) { tree.logWarning("Ignored multi-selection in single-mode for %o", dtnode); dtnode.bSelected = false; // Fixing bad input data (multi selection for mode:1) } */ if( dtnode.bSelected && opts.persist ){ pers.addSelect(dtnode.data.key); } } // Always expand, if it's below minExpandLevel // tree.logDebug ("%s._addChildNode(%o), l=%o", this, dtnode, dtnode.getLevel()); if ( opts.minExpandLevel >= dtnode.getLevel() ) { // tree.logDebug ("Force expand for %o", dtnode); this.bExpanded = true; } // In multi-hier mode, update the parents selection state // issue #82: only if not initializing, because the children may not exist yet // if( !dtnode.data.isStatusNode && opts.selectMode==3 && !isInitializing ) // dtnode._fixSelectionState(); // In multi-hier mode, update the parents selection state if( dtnode.bSelected && opts.selectMode==3 ) { var p = this; while( p ) { if( !p.hasSubSel ){ p._setSubSel(true); } p = p.parent; } } // render this node and the new child if ( tree.bEnableUpdate ){ this.render(); } return dtnode; }, addChild: function(obj, beforeNode) { /** * Add a node object as child. * * This should be the only place, where a DynaTreeNode is constructed! * (Except for the root node creation in the tree constructor) * * @param obj A JS object (may be recursive) or an array of those. * @param {DynaTreeNode} beforeNode (optional) sibling node. * * Data format: array of node objects, with optional 'children' attributes. * [ * { title: "t1", isFolder: true, ... } * { title: "t2", isFolder: true, ..., * children: [ * {title: "t2.1", ..}, * {..} * ] * } * ] * A simple object is also accepted instead of an array. * */ // this.tree.logDebug("%s.addChild(%o, %o)", this, obj, beforeNode); if(typeof(obj) == "string"){ throw "Invalid data type for " + obj; }else if( !obj || obj.length === 0 ){ // Passed null or undefined or empty array return; }else if( obj instanceof DynaTreeNode ){ return this._addChildNode(obj, beforeNode); } if( !obj.length ){ // Passed a single data object obj = [ obj ]; } var prevFlag = this.tree.enableUpdate(false); var tnFirst = null; for (var i=0, l=obj.length; i<l; i++) { var data = obj[i]; var dtnode = this._addChildNode(new DynaTreeNode(this, this.tree, data), beforeNode); if( !tnFirst ){ tnFirst = dtnode; } // Add child nodes recursively if( data.children ){ dtnode.addChild(data.children, null); } } this.tree.enableUpdate(prevFlag); return tnFirst; }, append: function(obj) { this.tree.logWarning("node.append() is deprecated (use node.addChild() instead)."); return this.addChild(obj, null); }, appendAjax: function(ajaxOptions) { var self = this; this.removeChildren(false, true); this.setLazyNodeStatus(DTNodeStatus_Loading); // Debug feature: force a delay, to simulate slow loading... if(ajaxOptions.debugLazyDelay){ var ms = ajaxOptions.debugLazyDelay; ajaxOptions.debugLazyDelay = 0; this.tree.logInfo("appendAjax: waiting for debugLazyDelay " + ms); setTimeout(function(){self.appendAjax(ajaxOptions);}, ms); return; } // Ajax option inheritance: $.ajaxSetup < $.ui.dynatree.prototype.options.ajaxDefaults < tree.options.ajaxDefaults < ajaxOptions var orgSuccess = ajaxOptions.success, orgError = ajaxOptions.error, eventType = "nodeLoaded.dynatree." + this.tree.$tree.attr("id") + "." + this.data.key; var options = $.extend({}, this.tree.options.ajaxDefaults, ajaxOptions, { success: function(data, textStatus, jqXHR){ // <this> is the request options // self.tree.logDebug("appendAjax().success"); var prevPhase = self.tree.phase; self.tree.phase = "init"; // postProcess is similar to the standard dataFilter hook, // but it is also called for JSONP if( options.postProcess ){ data = options.postProcess.call(this, data, this.dataType); } // Process ASPX WebMethod JSON object inside "d" property // http://code.google.com/p/dynatree/issues/detail?id=202 else if (data && data.hasOwnProperty("d")) { data = (typeof data.d) == "string" ? $.parseJSON(data.d) : data.d; } if(!$.isArray(data) || data.length !== 0){ self.addChild(data, null); } self.tree.phase = "postInit"; if( orgSuccess ){ orgSuccess.call(options, self, data, textStatus); } self.tree.logDebug("trigger " + eventType); self.tree.$tree.trigger(eventType, [self, true]); self.tree.phase = prevPhase; // This should be the last command, so node._isLoading is true // while the callbacks run self.setLazyNodeStatus(DTNodeStatus_Ok); if($.isArray(data) && data.length === 0){ // Set to [] which is interpreted as 'no children' for lazy // nodes self.childList = []; self.render(); } }, error: function(jqXHR, textStatus, errorThrown){ // <this> is the request options self.tree.logWarning("appendAjax failed:", textStatus, ":\n", jqXHR, "\n", errorThrown); if( orgError ){ orgError.call(options, self, jqXHR, textStatus, errorThrown); } self.tree.$tree.trigger(eventType, [self, false]); self.setLazyNodeStatus(DTNodeStatus_Error, {info: textStatus, tooltip: "" + errorThrown}); } }); $.ajax(options); }, move: function(targetNode, mode) { /**Move this node to targetNode. * 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'. * mode 'before': add this node as sibling before targetNode. * mode 'after': add this node as sibling after targetNode. */ var pos; if(this === targetNode){ return; } if( !this.parent ){ throw "Cannot move system root"; } if(mode === undefined || mode == "over"){ mode = "child"; } var prevParent = this.parent; var targetParent = (mode === "child") ? targetNode : targetNode.parent; if( targetParent.isDescendantOf(this) ){ throw "Cannot move a node to it's own descendant"; } // Unlink this node from current parent if( this.parent.childList.length == 1 ) { this.parent.childList = this.parent.data.isLazy ? [] : null; this.parent.bExpanded = false; } else { pos = $.inArray(this, this.parent.childList); if( pos < 0 ){ throw "Internal error"; } this.parent.childList.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.childList.push(this); break; case "before": // Insert this node before target node pos = $.inArray(targetNode, targetParent.childList); if( pos < 0 ){ throw "Internal error"; } targetParent.childList.splice(pos, 0, this); break; case "after": // Insert this node after target node pos = $.inArray(targetNode, targetParent.childList); if( pos < 0 ){ throw "Internal error"; } targetParent.childList.splice(pos+1, 0, this); break; default: throw "Invalid mode " + mode; } } else { targetParent.childList = [ this ]; } // Parent has no <ul> tag yet: if( !targetParent.ul ) { // This is the parent's first child: create UL tag // (Hidden, because it will be targetParent.ul = document.createElement("ul"); targetParent.ul.style.display = "none"; targetParent.li.appendChild(targetParent.ul); } // Issue 319: Add to target DOM parent (only if node was already rendered(expanded)) if(this.li){ targetParent.ul.appendChild(this.li); } if( this.tree !== targetNode.tree ) { // Fix node.tree for all source nodes this.visit(function(node){ node.tree = targetNode.tree; }, null, true); throw "Not yet implemented."; } // TODO: fix selection state // TODO: fix active state if( !prevParent.isDescendantOf(targetParent)) { prevParent.render(); } if( !targetParent.isDescendantOf(prevParent) ) { targetParent.render(); } // this.tree.redraw(); /* var tree = this.tree; var opts = tree.options; var pers = tree.persistence; // Always expand, if it's below minExpandLevel // tree.logDebug ("%s._addChildNode(%o), l=%o", this, dtnode, dtnode.getLevel()); if ( opts.minExpandLevel >= dtnode.getLevel() ) { // tree.logDebug ("Force expand for %o", dtnode); this.bExpanded = true; } // In multi-hier mode, update the parents selection state // issue #82: only if not initializing, because the children may not exist yet // if( !dtnode.data.isStatusNode && opts.selectMode==3 && !isInitializing ) // dtnode._fixSelectionState(); // In multi-hier mode, update the parents selection state if( dtnode.bSelected && opts.selectMode==3 ) { var p = this; while( p ) { if( !p.hasSubSel ) p._setSubSel(true); p = p.parent; } } // render this node and the new child if ( tree.bEnableUpdate ) this.render(); return dtnode; */ }, // --- end of class lastentry: undefined }; /************************************************************************* * class DynaTreeStatus */ var DynaTreeStatus = Class.create(); DynaTreeStatus._getTreePersistData = function(cookieId, cookieOpts) { // Static member: Return persistence information from cookies var ts = new DynaTreeStatus(cookieId, cookieOpts); ts.read(); return ts.toDict(); }; // Make available in global scope getDynaTreePersistData = DynaTreeStatus._getTreePersistData; // TODO: deprecated DynaTreeStatus.prototype = { // Constructor initialize: function(cookieId, cookieOpts) { // this._log("DynaTreeStatus: initialize"); if( cookieId === undefined ){ cookieId = $.ui.dynatree.prototype.options.cookieId; } cookieOpts = $.extend({}, $.ui.dynatree.prototype.options.cookie, cookieOpts); this.cookieId = cookieId; this.cookieOpts = cookieOpts; this.cookiesFound = undefined; this.activeKey = null; this.focusedKey = null; this.expandedKeyList = null; this.selectedKeyList = null; }, // member functions _log: function(msg) { // this.logDebug("_changeNodeList(%o): nodeList:%o, idx:%o", mode, nodeList, idx); Array.prototype.unshift.apply(arguments, ["debug"]); _log.apply(this, arguments); }, read: function() { // this._log("DynaTreeStatus: read"); // Read or init cookies. this.cookiesFound = false; var cookie = $.cookie(this.cookieId + "-active"); this.activeKey = ( cookie === null ) ? "" : cookie; if( cookie !== null ){ this.cookiesFound = true; } cookie = $.cookie(this.cookieId + "-focus"); this.focusedKey = ( cookie === null ) ? "" : cookie; if( cookie !== null ){ this.cookiesFound = true; } cookie = $.cookie(this.cookieId + "-expand"); this.expandedKeyList = ( cookie === null ) ? [] : cookie.split(","); if( cookie !== null ){ this.cookiesFound = true; } cookie = $.cookie(this.cookieId + "-select"); this.selectedKeyList = ( cookie === null ) ? [] : cookie.split(","); if( cookie !== null ){ this.cookiesFound = true; } }, write: function() { // this._log("DynaTreeStatus: write"); $.cookie(this.cookieId + "-active", ( this.activeKey === null ) ? "" : this.activeKey, this.cookieOpts); $.cookie(this.cookieId + "-focus", ( this.focusedKey === null ) ? "" : this.focusedKey, this.cookieOpts); $.cookie(this.cookieId + "-expand", ( this.expandedKeyList === null ) ? "" : this.expandedKeyList.join(","), this.cookieOpts); $.cookie(this.cookieId + "-select", ( this.selectedKeyList === null ) ? "" : this.selectedKeyList.join(","), this.cookieOpts); }, addExpand: function(key) { // this._log("addExpand(%o)", key); if( $.inArray(key, this.expandedKeyList) < 0 ) { this.expandedKeyList.push(key); $.cookie(this.cookieId + "-expand", this.expandedKeyList.join(","), this.cookieOpts); } }, clearExpand: function(key) { // this._log("clearExpand(%o)", key); var idx = $.inArray(key, this.expandedKeyList); if( idx >= 0 ) { this.expandedKeyList.splice(idx, 1); $.cookie(this.cookieId + "-expand", this.expandedKeyList.join(","), this.cookieOpts); } }, addSelect: function(key) { // this._log("addSelect(%o)", key); if( $.inArray(key, this.selectedKeyList) < 0 ) { this.selectedKeyList.push(key); $.cookie(this.cookieId + "-select", this.selectedKeyList.join(","), this.cookieOpts); } }, clearSelect: function(key) { // this._log("clearSelect(%o)", key); var idx = $.inArray(key, this.selectedKeyList); if( idx >= 0 ) { this.selectedKeyList.splice(idx, 1); $.cookie(this.cookieId + "-select", this.selectedKeyList.join(","), this.cookieOpts); } }, isReloading: function() { return this.cookiesFound === true; }, toDict: function() { return { cookiesFound: this.cookiesFound, activeKey: this.activeKey, focusedKey: this.activeKey, expandedKeyList: this.expandedKeyList, selectedKeyList: this.selectedKeyList }; }, // --- end of class lastentry: undefined }; /************************************************************************* * class DynaTree */ var DynaTree = Class.create(); // --- Static members ---------------------------------------------------------- DynaTree.version = "$Version: 1.2.4$"; /* DynaTree._initTree = function() { }; DynaTree._bind = function() { }; */ //--- Class members ------------------------------------------------------------ DynaTree.prototype = { // Constructor // initialize: function(divContainer, options) { initialize: function($widget) { // instance members this.phase = "init"; this.$widget = $widget; this.options = $widget.options; this.$tree = $widget.element; this.timer = null; // find container element this.divTree = this.$tree.get(0); // var parentPos = $(this.divTree).parent().offset(); // this.parentTop = parentPos.top; // this.parentLeft = parentPos.left; _initDragAndDrop(this); }, // member functions _load: function(callback) { var $widget = this.$widget; var opts = this.options, self = this; this.bEnableUpdate = true; this._nodeCount = 1; this.activeNode = null; this.focusNode = null; // Some deprecation warnings to help with migration if( opts.rootVisible !== undefined ){ this.logWarning("Option 'rootVisible' is no longer supported."); } if( opts.minExpandLevel < 1 ) { this.logWarning("Option 'minExpandLevel' must be >= 1."); opts.minExpandLevel = 1; } // _log("warn", "jQuery.support.boxModel " + jQuery.support.boxModel); // If a 'options.classNames' dictionary was passed, still use defaults // for undefined classes: if( opts.classNames !== $.ui.dynatree.prototype.options.classNames ) { opts.classNames = $.extend({}, $.ui.dynatree.prototype.options.classNames, opts.classNames); } if( opts.ajaxDefaults !== $.ui.dynatree.prototype.options.ajaxDefaults ) { opts.ajaxDefaults = $.extend({}, $.ui.dynatree.prototype.options.ajaxDefaults, opts.ajaxDefaults); } if( opts.dnd !== $.ui.dynatree.prototype.options.dnd ) { opts.dnd = $.extend({}, $.ui.dynatree.prototype.options.dnd, opts.dnd); } // Guess skin path, if not specified if(!opts.imagePath) { $("script").each( function () { var _rexDtLibName = /.*dynatree[^\/]*\.js$/i; if( this.src.search(_rexDtLibName) >= 0 ) { if( this.src.indexOf("/")>=0 ){ // issue #47 opts.imagePath = this.src.slice(0, this.src.lastIndexOf("/")) + "/skin/"; }else{ opts.imagePath = "skin/"; } self.logDebug("Guessing imagePath from '%s': '%s'", this.src, opts.imagePath); return false; // first match } }); } this.persistence = new DynaTreeStatus(opts.cookieId, opts.cookie); if( opts.persist ) { if( !$.cookie ){ _log("warn", "Please include jquery.cookie.js to use persistence."); } this.persistence.read(); } this.logDebug("DynaTree.persistence: %o", this.persistence.toDict()); // Cached tag strings this.cache = { tagEmpty: "<span class='" + opts.classNames.empty + "'></span>", tagVline: "<span class='" + opts.classNames.vline + "'></span>", tagExpander: "<span class='" + opts.classNames.expander + "'></span>", tagConnector: "<span class='" + opts.classNames.connector + "'></span>", tagNodeIcon: "<span class='" + opts.classNames.nodeIcon + "'></span>", tagCheckbox: "<span class='" + opts.classNames.checkbox + "'></span>", lastentry: undefined }; // Clear container, in case it contained some 'waiting' or 'error' text // for clients that don't support JS. // We don't do this however, if we try to load from an embedded UL element. if( opts.children || (opts.initAjax && opts.initAjax.url) || opts.initId ){ $(this.divTree).empty(); } var $ulInitialize = this.$tree.find(">ul:first").hide(); // Create the root element this.tnRoot = new DynaTreeNode(null, this, {}); this.tnRoot.bExpanded = true; this.tnRoot.render(); this.divTree.appendChild(this.tnRoot.ul); var root = this.tnRoot, isReloading = ( opts.persist && this.persistence.isReloading() ), isLazy = false, prevFlag = this.enableUpdate(false); this.logDebug("Dynatree._load(): read tree structure..."); // Init tree structure if( opts.children ) { // Read structure from node array root.addChild(opts.children); } else if( opts.initAjax && opts.initAjax.url ) { // Init tree from AJAX request isLazy = true; root.data.isLazy = true; this._reloadAjax(callback); } else if( opts.initId ) { // Init tree from another UL element this._createFromTag(root, $("#"+opts.initId)); } else { // Init tree from the first UL element inside the container <div> // var $ul = this.$tree.find(">ul:first").hide(); this._createFromTag(root, $ulInitialize); $ulInitialize.remove(); } this._checkConsistency(); // Fix part-sel flags if(!isLazy && opts.selectMode == 3){ root._updatePartSelectionState(); } // Render html markup this.logDebug("Dynatree._load(): render nodes..."); this.enableUpdate(prevFlag); // bind event handlers this.logDebug("Dynatree._load(): bind events..."); this.$widget.bind(); // --- Post-load processing this.logDebug("Dynatree._load(): postInit..."); this.phase = "postInit"; // In persist mode, make sure that cookies are written, even if they are empty if( opts.persist ) { this.persistence.write(); } // Set focus, if possible (this will also fire an event and write a cookie) if( this.focusNode && this.focusNode.isVisible() ) { this.logDebug("Focus on init: %o", this.focusNode); this.focusNode.focus(); } if( !isLazy ) { if( opts.onPostInit ) { opts.onPostInit.call(this, isReloading, false); } if( callback ){ callback.call(this, "ok"); } } this.phase = "idle"; }, _reloadAjax: function(callback) { // Reload var opts = this.options; if( ! opts.initAjax || ! opts.initAjax.url ){ throw "tree.reload() requires 'initAjax' mode."; } var pers = this.persistence; var ajaxOpts = $.extend({}, opts.initAjax); // Append cookie info to the request // this.logDebug("reloadAjax: key=%o, an.key:%o", pers.activeKey, this.activeNode?this.activeNode.data.key:"?"); if( ajaxOpts.addActiveKey ){ ajaxOpts.data.activeKey = pers.activeKey; } if( ajaxOpts.addFocusedKey ){ ajaxOpts.data.focusedKey = pers.focusedKey; } if( ajaxOpts.addExpandedKeyList ){ ajaxOpts.data.expandedKeyList = pers.expandedKeyList.join(","); } if( ajaxOpts.addSelectedKeyList ){ ajaxOpts.data.selectedKeyList = pers.selectedKeyList.join(","); } // Set up onPostInit callback to be called when Ajax returns if( ajaxOpts.success ){ this.logWarning("initAjax: success callback is ignored; use onPostInit instead."); } if( ajaxOpts.error ){ this.logWarning("initAjax: error callback is ignored; use onPostInit instead."); } var isReloading = pers.isReloading(); ajaxOpts.success = function(dtnode, data, textStatus) { if(opts.selectMode == 3){ dtnode.tree.tnRoot._updatePartSelectionState(); } if(opts.onPostInit){ opts.onPostInit.call(dtnode.tree, isReloading, false); } if(callback){ callback.call(dtnode.tree, "ok"); } }; ajaxOpts.error = function(dtnode, XMLHttpRequest, textStatus, errorThrown) { if(opts.onPostInit){ opts.onPostInit.call(dtnode.tree, isReloading, true, XMLHttpRequest, textStatus, errorThrown); } if(callback){ callback.call(dtnode.tree, "error", XMLHttpRequest, textStatus, errorThrown); } }; // } this.logDebug("Dynatree._init(): send Ajax request..."); this.tnRoot.appendAjax(ajaxOpts); }, toString: function() { // return "DynaTree '" + this.options.title + "'"; return "Dynatree '" + this.$tree.attr("id") + "'"; }, toDict: function() { return this.tnRoot.toDict(true); }, serializeArray: function(stopOnParents) { // Return a JavaScript array of objects, ready to be encoded as a JSON // string for selected nodes var nodeList = this.getSelectedNodes(stopOnParents), name = this.$tree.attr("name") || this.$tree.attr("id"), arr = []; for(var i=0, l=nodeList.length; i<l; i++){ arr.push({name: name, value: nodeList[i].data.key}); } return arr; }, getPersistData: function() { return this.persistence.toDict(); }, logDebug: function(msg) { if( this.options.debugLevel >= 2 ) { Array.prototype.unshift.apply(arguments, ["debug"]); _log.apply(this, arguments); } }, logInfo: function(msg) { if( this.options.debugLevel >= 1 ) { Array.prototype.unshift.apply(arguments, ["info"]); _log.apply(this, arguments); } }, logWarning: function(msg) { Array.prototype.unshift.apply(arguments, ["warn"]); _log.apply(this, arguments); }, isInitializing: function() { return ( this.phase=="init" || this.phase=="postInit" ); }, isReloading: function() { return ( this.phase=="init" || this.phase=="postInit" ) && this.options.persist && this.persistence.cookiesFound; }, isUserEvent: function() { return ( this.phase=="userEvent" ); }, redraw: function() { // this.logDebug("dynatree.redraw()..."); this.tnRoot.render(false, false); // this.logDebug("dynatree.redraw() done."); }, renderInvisibleNodes: function() { this.tnRoot.render(false, true); }, reload: function(callback) { this._load(callback); }, getRoot: function() { return this.tnRoot; }, enable: function() { this.$widget.enable(); }, disable: function() { this.$widget.disable(); }, getNodeByKey: function(key) { // Search the DOM by element ID (assuming this is faster than traversing all nodes). // $("#...") has problems, if the key contains '.', so we use getElementById() var el = document.getElementById(this.options.idPrefix + key); if( el ){ return el.dtnode ? el.dtnode : null; } // Not found in the DOM, but still may be in an unrendered part of tree var match = null; this.visit(function(node){ // window.console.log("%s", node); if(node.data.key === key) { match = node; return false; } }, true); return match; }, getActiveNode: function() { return this.activeNode; }, reactivate: function(setFocus) { // Re-fire onQueryActivate and onActivate events. var node = this.activeNode; // this.logDebug("reactivate %o", node); if( node ) { this.activeNode = null; // Force re-activating node.activate(); if( setFocus ){ node.focus(); } } }, getSelectedNodes: function(stopOnParents) { var nodeList = []; this.tnRoot.visit(function(node){ if( node.bSelected ) { nodeList.push(node); if( stopOnParents === true ){ return "skip"; // stop processing this branch } } }); return nodeList; }, activateKey: function(key) { var dtnode = (key === null) ? null : this.getNodeByKey(key); if( !dtnode ) { if( this.activeNode ){ this.activeNode.deactivate(); } this.activeNode = null; return null; } dtnode.focus(); dtnode.activate(); return dtnode; }, loadKeyPath: function(keyPath, callback) { var segList = keyPath.split(this.options.keyPathSeparator); // Remove leading '/' if(segList[0] === ""){ segList.shift(); } // Remove leading system root key if(segList[0] == this.tnRoot.data.key){ this.logDebug("Removed leading root key."); segList.shift(); } keyPath = segList.join(this.options.keyPathSeparator); return this.tnRoot._loadKeyPath(keyPath, callback); }, selectKey: function(key, select) { var dtnode = this.getNodeByKey(key); if( !dtnode ){ return null; } dtnode.select(select); return dtnode; }, enableUpdate: function(bEnable) { if ( this.bEnableUpdate==bEnable ){ return bEnable; } this.bEnableUpdate = bEnable; if ( bEnable ){ this.redraw(); } return !bEnable; // return previous value }, count: function() { return this.tnRoot.countChildren(); }, visit: function(fn, includeRoot) { return this.tnRoot.visit(fn, includeRoot); }, _createFromTag: function(parentTreeNode, $ulParent) { // Convert a <UL>...</UL> list into children of the parent tree node. var self = this; /* TODO: better? this.$lis = $("li:has(a[href])", this.element); this.$tabs = this.$lis.map(function() { return $("a", this)[0]; }); */ $ulParent.find(">li").each(function() { var $li = $(this), $liSpan = $li.find(">span:first"), $liA = $li.find(">a:first"), title, href = null, target = null, tooltip; if( $liSpan.length ) { // If a <li><span> tag is specified, use it literally. title = $liSpan.html(); } else if( $liA.length ) { title = $liA.html(); href = $liA.attr("href"); target = $liA.attr("target"); tooltip = $liA.attr("title"); } else { // If only a <li> tag is specified, use the trimmed string up to // the next child <ul> tag. title = $li.html(); var iPos = title.search(/<ul/i); if( iPos >= 0 ){ title = $.trim(title.substring(0, iPos)); }else{ title = $.trim(title); } // self.logDebug("%o", title); } // Parse node options from ID, title and class attributes var data = { title: title, tooltip: tooltip, isFolder: $li.hasClass("folder"), isLazy: $li.hasClass("lazy"), expand: $li.hasClass("expanded"), select: $li.hasClass("selected"), activate: $li.hasClass("active"), focus: $li.hasClass("focused"), noLink: $li.hasClass("noLink") }; if( href ){ data.href = href; data.target = target; } if( $li.attr("title") ){ data.tooltip = $li.attr("title"); // overrides <a title='...'> } if( $li.attr("id") ){ data.key = "" + $li.attr("id"); } // If a data attribute is present, evaluate as a JavaScript object if( $li.attr("data") ) { var dataAttr = $.trim($li.attr("data")); if( dataAttr ) { if( dataAttr.charAt(0) != "{" ){ dataAttr = "{" + dataAttr + "}"; } try { $.extend(data, eval("(" + dataAttr + ")")); } catch(e) { throw ("Error parsing node data: " + e + "\ndata:\n'" + dataAttr + "'"); } } } var childNode = parentTreeNode.addChild(data); // Recursive reading of child nodes, if LI tag contains an UL tag var $ul = $li.find(">ul:first"); if( $ul.length ) { self._createFromTag(childNode, $ul); // must use 'self', because 'this' is the each() context } }); }, _checkConsistency: function() { // this.logDebug("tree._checkConsistency() NOT IMPLEMENTED - %o", this); }, _setDndStatus: function(sourceNode, targetNode, helper, hitMode, accept) { // hitMode: 'after', 'before', 'over', 'out', 'start', 'stop' var $source = sourceNode ? $(sourceNode.span) : null, $target = $(targetNode.span); if( !this.$dndMarker ) { this.$dndMarker = $("<div id='dynatree-drop-marker'></div>") .hide() .css({"z-index": 1000}) .prependTo($(this.divTree).parent()); // logMsg("Creating marker: %o", this.$dndMarker); } /* if(hitMode === "start"){ } if(hitMode === "stop"){ // sourceNode.removeClass("dynatree-drop-target"); } */ if(hitMode === "after" || hitMode === "before" || hitMode === "over"){ // $source && $source.addClass("dynatree-drag-source"); // $target.addClass("dynatree-drop-target"); var markerOffset = "0 0"; switch(hitMode){ case "before": this.$dndMarker.removeClass("dynatree-drop-after dynatree-drop-over"); this.$dndMarker.addClass("dynatree-drop-before"); markerOffset = "0 -8"; break; case "after": this.$dndMarker.removeClass("dynatree-drop-before dynatree-drop-over"); this.$dndMarker.addClass("dynatree-drop-after"); markerOffset = "0 8"; break; default: this.$dndMarker.removeClass("dynatree-drop-after dynatree-drop-before"); this.$dndMarker.addClass("dynatree-drop-over"); $target.addClass("dynatree-drop-target"); markerOffset = "8 0"; } // logMsg("Creating marker: %o", this.$dndMarker); // logMsg(" $target.offset=%o", $target); // logMsg(" pos/$target.offset=%o", pos); // logMsg(" $target.position=%o", $target.position()); // logMsg(" $target.offsetParent=%o, ot:%o", $target.offsetParent(), $target.offsetParent().offset()); // logMsg(" $(this.divTree).offset=%o", $(this.divTree).offset()); // logMsg(" $(this.divTree).parent=%o", $(this.divTree).parent()); // var pos = $target.offset(); // var parentPos = $target.offsetParent().offset(); // var bodyPos = $target.offsetParent().offset(); this.$dndMarker .show() .position({ my: "left top", at: "left top", of: $target, offset: markerOffset }); // helper.addClass("dynatree-drop-hover"); } else { // $source && $source.removeClass("dynatree-drag-source"); $target.removeClass("dynatree-drop-target"); this.$dndMarker.hide(); // helper.removeClass("dynatree-drop-hover"); } if(hitMode === "after"){ $target.addClass("dynatree-drop-after"); } else { $target.removeClass("dynatree-drop-after"); } if(hitMode === "before"){ $target.addClass("dynatree-drop-before"); } else { $target.removeClass("dynatree-drop-before"); } if(accept === true){ if($source){ $source.addClass("dynatree-drop-accept"); } $target.addClass("dynatree-drop-accept"); helper.addClass("dynatree-drop-accept"); }else{ if($source){ $source.removeClass("dynatree-drop-accept"); } $target.removeClass("dynatree-drop-accept"); helper.removeClass("dynatree-drop-accept"); } if(accept === false){ if($source){ $source.addClass("dynatree-drop-reject"); } $target.addClass("dynatree-drop-reject"); helper.addClass("dynatree-drop-reject"); }else{ if($source){ $source.removeClass("dynatree-drop-reject"); } $target.removeClass("dynatree-drop-reject"); helper.removeClass("dynatree-drop-reject"); } }, _onDragEvent: function(eventName, node, otherNode, event, ui, draggable) { /** * Handles drag'n'drop functionality. * * A standard jQuery drag-and-drop process may generate these calls: * * draggable helper(): * _onDragEvent("helper", sourceNode, null, event, null, null); * 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); */ // if(eventName !== "over"){ // this.logDebug("tree._onDragEvent(%s, %o, %o) - %o", eventName, node, otherNode, this); // } var opts = this.options, dnd = this.options.dnd, res = null, nodeTag = $(node.span), hitMode, enterResponse; switch (eventName) { case "helper": // Only event and node argument is available var $helper = $("<div class='dynatree-drag-helper'><span class='dynatree-drag-helper-img' /></div>") .append($(event.target).closest(".dynatree-title").clone()); // .append($(event.target).closest('a').clone()); // issue 244: helper should be child of scrollParent $("ul.dynatree-container", node.tree.divTree).append($helper); // $(node.tree.divTree).append($helper); // Attach node reference to helper object $helper.data("dtSourceNode", node); // this.logDebug("helper=%o", $helper); // this.logDebug("helper.sourceNode=%o", $helper.data("dtSourceNode")); res = $helper; break; case "start": if(node.isStatusNode()) { res = false; } else if(dnd.onDragStart) { res = dnd.onDragStart(node); } if(res === false) { this.logDebug("tree.onDragStart() cancelled"); //draggable._clear(); // NOTE: the return value seems to be ignored (drag is not canceled, when false is returned) ui.helper.trigger("mouseup"); ui.helper.hide(); } else { nodeTag.addClass("dynatree-drag-source"); } break; case "enter": res = dnd.onDragEnter ? dnd.onDragEnter(node, otherNode) : null; if(!res){ // convert null, undefined, false to false res = false; }else{ res = { over: ((res === true) || (res === "over") || $.inArray("over", res) >= 0), before: ((res === true) || (res === "before") || $.inArray("before", res) >= 0), after: ((res === true) || (res === "after") || $.inArray("after", res) >= 0) }; } ui.helper.data("enterResponse", res); // this.logDebug("helper.enterResponse: %o", res); break; case "over": enterResponse = ui.helper.data("enterResponse"); hitMode = null; if(enterResponse === false){ // Don't call onDragOver if onEnter returned false. // issue 332 // break; } else if(typeof enterResponse === "string") { // Use hitMode from onEnter if provided. hitMode = enterResponse; } else { // Calculate hitMode from relative cursor position. var nodeOfs = nodeTag.offset(); // var relPos = { x: event.clientX - nodeOfs.left, // y: event.clientY - nodeOfs.top }; // nodeOfs.top += this.parentTop; // nodeOfs.left += this.parentLeft; var relPos = { x: event.pageX - nodeOfs.left, y: event.pageY - nodeOfs.top }; var relPos2 = { x: relPos.x / nodeTag.width(), y: relPos.y / nodeTag.height() }; // this.logDebug("event.page: %s/%s", event.pageX, event.pageY); // this.logDebug("event.client: %s/%s", event.clientX, event.clientY); // this.logDebug("nodeOfs: %s/%s", nodeOfs.left, nodeOfs.top); //// this.logDebug("parent: %s/%s", this.parentLeft, this.parentTop); // this.logDebug("relPos: %s/%s", relPos.x, relPos.y); // this.logDebug("relPos2: %s/%s", relPos2.x, relPos2.y); 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.logDebug(" drop over source node prevented"); hitMode = null; }else if(hitMode === "before" && otherNode && node === otherNode.getNextSibling()){ // this.logDebug(" drop after source node prevented"); hitMode = null; }else if(hitMode === "after" && otherNode && node === otherNode.getPrevSibling()){ // this.logDebug(" drop before source node prevented"); hitMode = null; }else if(hitMode === "over" && otherNode && otherNode.parent === node && otherNode.isLastSibling() ){ // this.logDebug(" drop last child over own parent prevented"); hitMode = null; } } // this.logDebug("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 === "over" && dnd.autoExpandMS && node.hasChildren() !== false && !node.bExpanded) { node.scheduleAction("expand", dnd.autoExpandMS); } if(hitMode && dnd.onDragOver){ res = dnd.onDragOver(node, otherNode, hitMode); if(res === "over" || res === "before" || res === "after") { hitMode = res; } } // issue 332 // this._setDndStatus(otherNode, node, ui.helper, hitMode, res!==false); this._setDndStatus(otherNode, node, ui.helper, hitMode, res!==false && hitMode !== null); break; case "drop": // issue 286: don't trigger onDrop, if DnD status is 'reject' var isForbidden = ui.helper.hasClass("dynatree-drop-reject"); hitMode = ui.helper.data("hitMode"); if(hitMode && dnd.onDrop && !isForbidden){ dnd.onDrop(node, otherNode, hitMode, ui, draggable); } break; case "leave": // Cancel pending expand request node.scheduleAction("cancel"); ui.helper.data("enterResponse", null); ui.helper.data("hitMode", null); this._setDndStatus(otherNode, node, ui.helper, "out", undefined); if(dnd.onDragLeave){ dnd.onDragLeave(node, otherNode); } break; case "stop": nodeTag.removeClass("dynatree-drag-source"); if(dnd.onDragStop){ dnd.onDragStop(node); } break; default: throw "Unsupported drag event: " + eventName; } return res; }, cancelDrag: function() { var dd = $.ui.ddmanager.current; if(dd){ dd.cancel(); } }, // --- end of class lastentry: undefined }; /************************************************************************* * Widget $(..).dynatree */ $.widget("ui.dynatree", { /* init: function() { // ui.core 1.6 renamed init() to _init(): this stub assures backward compatibility _log("warn", "ui.dynatree.init() was called; you should upgrade to jquery.ui.core.js v1.8 or higher."); return this._init(); }, */ _init: function() { // if( parseFloat($.ui.version) < 1.8 ) { if(versionCompare($.ui.version, "1.8") < 0){ // jquery.ui.core 1.8 renamed _init() to _create(): this stub assures backward compatibility if(this.options.debugLevel >= 0){ _log("warn", "ui.dynatree._init() was called; you should upgrade to jquery.ui.core.js v1.8 or higher."); } return this._create(); } // jquery.ui.core 1.8 still uses _init() to perform "default functionality" if(this.options.debugLevel >= 2){ _log("debug", "ui.dynatree._init() was called; no current default functionality."); } }, _create: function() { var opts = this.options; if(opts.debugLevel >= 1){ logMsg("Dynatree._create(): version='%s', debugLevel=%o.", $.ui.dynatree.version, this.options.debugLevel); } // The widget framework supplies this.element and this.options. this.options.event += ".dynatree"; // namespace event var divTree = this.element.get(0); /* // Clear container, in case it contained some 'waiting' or 'error' text // for clients that don't support JS if( opts.children || (opts.initAjax && opts.initAjax.url) || opts.initId ) $(divTree).empty(); */ // Create the DynaTree object this.tree = new DynaTree(this); this.tree._load(); this.tree.logDebug("Dynatree._init(): done."); }, bind: function() { // Prevent duplicate binding this.unbind(); var eventNames = "click.dynatree dblclick.dynatree"; if( this.options.keyboard ){ // Note: leading ' '! eventNames += " keypress.dynatree keydown.dynatree"; } this.element.bind(eventNames, function(event){ var dtnode = $.ui.dynatree.getNode(event.target); if( !dtnode ){ return true; // Allow bubbling of other events } var tree = dtnode.tree; var o = tree.options; tree.logDebug("event(%s): dtnode: %s", event.type, dtnode); var prevPhase = tree.phase; tree.phase = "userEvent"; try { switch(event.type) { case "click": return ( o.onClick && o.onClick.call(tree, dtnode, event)===false ) ? false : dtnode._onClick(event); case "dblclick": return ( o.onDblClick && o.onDblClick.call(tree, dtnode, event)===false ) ? false : dtnode._onDblClick(event); case "keydown": return ( o.onKeydown && o.onKeydown.call(tree, dtnode, event)===false ) ? false : dtnode._onKeydown(event); case "keypress": return ( o.onKeypress && o.onKeypress.call(tree, dtnode, event)===false ) ? false : dtnode._onKeypress(event); } } catch(e) { var _ = null; // issue 117 tree.logWarning("bind(%o): dtnode: %o, error: %o", event, dtnode, e); } finally { tree.phase = prevPhase; } }); // focus/blur don't bubble, i.e. are not delegated to parent <div> tags, // so we use the addEventListener capturing phase. // See http://www.howtocreate.co.uk/tutorials/javascript/domevents function __focusHandler(event) { // Handles blur and focus. // Fix event for IE: // doesn't pass JSLint: // event = arguments[0] = $.event.fix( event || window.event ); // what jQuery does: // var args = jQuery.makeArray( arguments ); // event = args[0] = jQuery.event.fix( event || window.event ); event = $.event.fix( event || window.event ); var dtnode = $.ui.dynatree.getNode(event.target); return dtnode ? dtnode._onFocus(event) : false; } var div = this.tree.divTree; if( div.addEventListener ) { div.addEventListener("focus", __focusHandler, true); div.addEventListener("blur", __focusHandler, true); } else { div.onfocusin = div.onfocusout = __focusHandler; } // EVENTS // disable click if event is configured to something else // if (!(/^click/).test(o.event)) // this.$tabs.bind("click.tabs", function() { return false; }); }, unbind: function() { this.element.unbind(".dynatree"); }, /* TODO: we could handle option changes during runtime here (maybe to re-render, ...) setData: function(key, value) { this.tree.logDebug("dynatree.setData('" + key + "', '" + value + "')"); }, */ enable: function() { this.bind(); // Call default disable(): remove -disabled from css: $.Widget.prototype.enable.apply(this, arguments); }, disable: function() { this.unbind(); // Call default disable(): add -disabled to css: $.Widget.prototype.disable.apply(this, arguments); }, // --- getter methods (i.e. NOT returning a reference to $) getTree: function() { return this.tree; }, getRoot: function() { return this.tree.getRoot(); }, getActiveNode: function() { return this.tree.getActiveNode(); }, getSelectedNodes: function() { return this.tree.getSelectedNodes(); }, // ------------------------------------------------------------------------ lastentry: undefined }); // The following methods return a value (thus breaking the jQuery call chain): if(versionCompare($.ui.version, "1.8") < 0){ //if( parseFloat($.ui.version) < 1.8 ) { $.ui.dynatree.getter = "getTree getRoot getActiveNode getSelectedNodes"; } /******************************************************************************* * Tools in ui.dynatree namespace */ $.ui.dynatree.version = "$Version: 1.2.4$"; /** * Return a DynaTreeNode object for a given DOM element */ $.ui.dynatree.getNode = function(el) { if(el instanceof DynaTreeNode){ return el; // el already was a DynaTreeNode } if(el.selector !== undefined){ el = el[0]; // el was a jQuery object: use the DOM element } // TODO: for some reason $el.parents("[dtnode]") does not work (jQuery 1.6.1) // maybe, because dtnode is a property, not an attribute while( el ) { if(el.dtnode) { return el.dtnode; } el = el.parentNode; } return null; /* var $el = el.selector === undefined ? $(el) : el, // parent = $el.closest("[dtnode]"), // parent = $el.parents("[dtnode]").first(), useProp = (typeof $el.prop == "function"), node; $el.parents().each(function(){ node = useProp ? $(this).prop("dtnode") : $(this).attr("dtnode"); if(node){ return false; } }); return node; */ }; /**Return persistence information from cookies.*/ $.ui.dynatree.getPersistData = DynaTreeStatus._getTreePersistData; /******************************************************************************* * Plugin default options: */ $.ui.dynatree.prototype.options = { title: "Dynatree", // Tree's name (only used for debug output) minExpandLevel: 1, // 1: root node is not collapsible imagePath: null, // Path to a folder containing icons. Defaults to 'skin/' subdirectory. children: null, // Init tree structure from this object array. initId: null, // Init tree structure from a <ul> element with this ID. initAjax: null, // Ajax options used to initialize the tree strucuture. autoFocus: true, // Set focus to first child, when expanding or lazy-loading. keyboard: true, // Support keyboard navigation. persist: false, // Persist expand-status to a cookie autoCollapse: false, // Automatically collapse all siblings, when a node is expanded. clickFolderMode: 3, // 1:activate, 2:expand, 3:activate and expand activeVisible: true, // Make sure, active nodes are visible (expanded). checkbox: false, // Show checkboxes. selectMode: 2, // 1:single, 2:multi, 3:multi-hier fx: null, // Animations, e.g. null or { height: "toggle", duration: 200 } noLink: false, // Use <span> instead of <a> tags for all nodes // Low level event handlers: onEvent(dtnode, event): return false, to stop default processing onClick: null, // null: generate focus, expand, activate, select events. onDblClick: null, // (No default actions.) onKeydown: null, // null: generate keyboard navigation (focus, expand, activate). onKeypress: null, // (No default actions.) onFocus: null, // null: set focus to node. onBlur: null, // null: remove focus from node. // Pre-event handlers onQueryEvent(flag, dtnode): return false, to stop processing onQueryActivate: null, // Callback(flag, dtnode) before a node is (de)activated. onQuerySelect: null, // Callback(flag, dtnode) before a node is (de)selected. onQueryExpand: null, // Callback(flag, dtnode) before a node is expanded/collpsed. // High level event handlers onPostInit: null, // Callback(isReloading, isError) when tree was (re)loaded. onActivate: null, // Callback(dtnode) when a node is activated. onDeactivate: null, // Callback(dtnode) when a node is deactivated. onSelect: null, // Callback(flag, dtnode) when a node is (de)selected. onExpand: null, // Callback(flag, dtnode) when a node is expanded/collapsed. onLazyRead: null, // Callback(dtnode) when a lazy node is expanded for the first time. onCustomRender: null, // Callback(dtnode) before a node is rendered. Return a HTML string to override. onCreate: null, // Callback(dtnode, nodeSpan) after a node was rendered for the first time. onRender: null, // Callback(dtnode, nodeSpan) after a node was rendered. // postProcess is similar to the standard dataFilter hook, // but it is also called for JSONP postProcess: null, // Callback(data, dataType) before an Ajax result is passed to dynatree // Drag'n'drop support dnd: { // Make tree nodes draggable: onDragStart: null, // Callback(sourceNode), return true, to enable dnd onDragStop: null, // Callback(sourceNode) // helper: null, // Make tree nodes accept draggables autoExpandMS: 1000, // Expand nodes after n milliseconds of hovering. preventVoidMoves: true, // Prevent dropping nodes 'before self', etc. onDragEnter: null, // Callback(targetNode, sourceNode) onDragOver: null, // Callback(targetNode, sourceNode, hitMode) onDrop: null, // Callback(targetNode, sourceNode, hitMode) onDragLeave: null // Callback(targetNode, sourceNode) }, ajaxDefaults: { // Used by initAjax option cache: false, // false: Append random '_' argument to the request url to prevent caching. timeout: 0, // >0: Make sure we get an ajax error for invalid URLs dataType: "json" // Expect json format and pass json object to callbacks. }, strings: { loading: "Loading&#8230;", loadError: "Load error!" }, generateIds: false, // Generate id attributes like <span id='dynatree-id-KEY'> idPrefix: "dynatree-id-", // Used to generate node id's like <span id="dynatree-id-<key>">. keyPathSeparator: "/", // Used by node.getKeyPath() and tree.loadKeyPath(). // cookieId: "dynatree-cookie", // Choose a more unique name, to allow multiple trees. cookieId: "dynatree", // Choose a more unique name, to allow multiple trees. cookie: { expires: null //7, // Days or Date; null: session cookie // path: "/", // Defaults to current page // domain: "jquery.com", // secure: true }, // Class names used, when rendering the HTML markup. // Note: if only single entries are passed for options.classNames, all other // values are still set to default. classNames: { container: "dynatree-container", node: "dynatree-node", folder: "dynatree-folder", // document: "dynatree-document", empty: "dynatree-empty", vline: "dynatree-vline", expander: "dynatree-expander", connector: "dynatree-connector", checkbox: "dynatree-checkbox", nodeIcon: "dynatree-icon", title: "dynatree-title", noConnector: "dynatree-no-connector", nodeError: "dynatree-statusnode-error", nodeWait: "dynatree-statusnode-wait", hidden: "dynatree-hidden", combinedExpanderPrefix: "dynatree-exp-", combinedIconPrefix: "dynatree-ico-", nodeLoading: "dynatree-loading", // disabled: "dynatree-disabled", hasChildren: "dynatree-has-children", active: "dynatree-active", selected: "dynatree-selected", expanded: "dynatree-expanded", lazy: "dynatree-lazy", focused: "dynatree-focused", partsel: "dynatree-partsel", lastsib: "dynatree-lastsib" }, debugLevel: 1, // ------------------------------------------------------------------------ lastentry: undefined }; // if(versionCompare($.ui.version, "1.8") < 0){ //if( parseFloat($.ui.version) < 1.8 ) { $.ui.dynatree.defaults = $.ui.dynatree.prototype.options; } /******************************************************************************* * Reserved data attributes for a tree node. */ $.ui.dynatree.nodedatadefaults = { title: null, // (required) Displayed name of the node (html is allowed here) key: null, // May be used with activate(), select(), find(), ... isFolder: false, // Use a folder icon. Also the node is expandable but not selectable. isLazy: false, // Call onLazyRead(), when the node is expanded for the first time to allow for delayed creation of children. tooltip: null, // Show this popup text. href: null, // Added to the generated <a> tag. icon: null, // Use a custom image (filename relative to tree.options.imagePath). 'null' for default icon, 'false' for no icon. addClass: null, // Class name added to the node's span tag. noLink: false, // Use <span> instead of <a> tag for this node activate: false, // Initial active status. focus: false, // Initial focused status. expand: false, // Initial expanded status. select: false, // Initial selected status. hideCheckbox: false, // Suppress checkbox display for this node. unselectable: false, // Prevent selection. // disabled: false, // The following attributes are only valid if passed to some functions: children: null, // Array of child nodes. // NOTE: we can also add custom attributes here. // This may then also be used in the onActivate(), onSelect() or onLazyTree() callbacks. // ------------------------------------------------------------------------ lastentry: undefined }; /******************************************************************************* * Drag and drop support */ function _initDragAndDrop(tree) { var dnd = tree.options.dnd || null; // Register 'connectToDynatree' option with ui.draggable if(dnd && (dnd.onDragStart || dnd.onDrop)) { _registerDnd(); } // Attach ui.draggable to this Dynatree instance if(dnd && dnd.onDragStart ) { tree.$tree.draggable({ addClasses: false, appendTo: "body", containment: false, delay: 0, distance: 4, revert: false, scroll: true, // issue 244: enable scrolling (if ul.dynatree-container) scrollSpeed: 7, scrollSensitivity: 10, // Delegate draggable.start, drag, and stop events to our handler connectToDynatree: true, // Let source tree create the helper element helper: function(event) { var sourceNode = $.ui.dynatree.getNode(event.target); if(!sourceNode){ // issue 211 return "<div></div>"; } return sourceNode.tree._onDragEvent("helper", sourceNode, null, event, null, null); }, start: function(event, ui) { // See issues 211, 268, 278 // var sourceNode = $.ui.dynatree.getNode(event.target); var sourceNode = ui.helper.data("dtSourceNode"); return !!sourceNode; // Abort dragging if no Node could be found }, _last: null }); } // Attach ui.droppable to this Dynatree instance if(dnd && dnd.onDrop) { tree.$tree.droppable({ addClasses: false, tolerance: "intersect", greedy: false, _last: null }); } } //--- Extend ui.draggable event handling -------------------------------------- var didRegisterDnd = false; var _registerDnd = function() { if(didRegisterDnd){ return; } // Register proxy-functions for draggable.start/drag/stop $.ui.plugin.add("draggable", "connectToDynatree", { start: function(event, ui) { // issue 386 var draggable = $(this).data("ui-draggable") || $(this).data("draggable"), sourceNode = ui.helper.data("dtSourceNode") || null; // logMsg("draggable-connectToDynatree.start, %s", sourceNode); // logMsg(" this: %o", this); // logMsg(" event: %o", event); // logMsg(" draggable: %o", draggable); // logMsg(" ui: %o", ui); if(sourceNode) { // Adjust helper offset, so cursor is slightly outside top/left corner // draggable.offset.click.top -= event.target.offsetTop; // draggable.offset.click.left -= event.target.offsetLeft; draggable.offset.click.top = -2; draggable.offset.click.left = + 16; // logMsg(" draggable2: %o", draggable); // logMsg(" draggable.offset.click FIXED: %s/%s", draggable.offset.click.left, draggable.offset.click.top); // Trigger onDragStart event // TODO: when called as connectTo..., the return value is ignored(?) return sourceNode.tree._onDragEvent("start", sourceNode, null, event, ui, draggable); } }, drag: function(event, ui) { // issue 386 var draggable = $(this).data("ui-draggable") || $(this).data("draggable"), sourceNode = ui.helper.data("dtSourceNode") || null, prevTargetNode = ui.helper.data("dtTargetNode") || null, targetNode = $.ui.dynatree.getNode(event.target); // logMsg("$.ui.dynatree.getNode(%o): %s", event.target, targetNode); // logMsg("connectToDynatree.drag: helper: %o", ui.helper[0]); 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 non-dynatree element is dragged // We ignore it: var isHelper = $(event.target).closest("div.dynatree-drag-helper,#dynatree-drop-marker").length > 0; if(isHelper){ // logMsg("Drag event over helper: ignored."); return; } } // logMsg("draggable-connectToDynatree.drag: targetNode(from event): %s, dtTargetNode: %s", targetNode, ui.helper.data("dtTargetNode")); ui.helper.data("dtTargetNode", targetNode); // Leaving a tree node if(prevTargetNode && prevTargetNode !== targetNode ) { prevTargetNode.tree._onDragEvent("leave", prevTargetNode, sourceNode, event, ui, draggable); } if(targetNode){ if(!targetNode.tree.options.dnd.onDrop) { // not enabled as drop target // noop(); // Keep JSLint happy } else if(targetNode === prevTargetNode) { // Moving over same node targetNode.tree._onDragEvent("over", targetNode, sourceNode, event, ui, draggable); }else{ // Entering this node first time targetNode.tree._onDragEvent("enter", targetNode, sourceNode, event, ui, draggable); } } // else go ahead with standard event handling }, stop: function(event, ui) { // issue 386 var draggable = $(this).data("ui-draggable") || $(this).data("draggable"), sourceNode = ui.helper.data("dtSourceNode") || null, targetNode = ui.helper.data("dtTargetNode") || null, mouseDownEvent = draggable._mouseDownEvent, eventType = event.type, dropped = (eventType == "mouseup" && event.which == 1); logMsg("draggable-connectToDynatree.stop: targetNode(from event): %s, dtTargetNode: %s", targetNode, ui.helper.data("dtTargetNode")); // logMsg("draggable-connectToDynatree.stop, %s", sourceNode); // logMsg(" type: %o, downEvent: %o, upEvent: %o", eventType, mouseDownEvent, event); // logMsg(" targetNode: %o", targetNode); if(!dropped){ logMsg("Drag was cancelled"); } if(targetNode) { if(dropped){ targetNode.tree._onDragEvent("drop", targetNode, sourceNode, event, ui, draggable); } targetNode.tree._onDragEvent("leave", targetNode, sourceNode, event, ui, draggable); } if(sourceNode){ sourceNode.tree._onDragEvent("stop", sourceNode, null, event, ui, draggable); } } }); didRegisterDnd = true; }; // --------------------------------------------------------------------------- }(jQuery));