/static/scripts/jquery.dynatree.js

https://bitbucket.org/cistrome/cistrome-harvard/ · JavaScript · 2034 lines · 1446 code · 204 blank · 384 comment · 428 complexity · cebbdac6e84b5d05b26e537eff9a82d1 MD5 · raw file

Large files are truncated click here to view the full file

  1. /*************************************************************************
  2. jquery.dynatree.js
  3. Dynamic tree view control, with support for lazy loading of branches.
  4. Copyright (c) 2008-2010, Martin Wendt (http://wwWendt.de)
  5. Dual licensed under the MIT or GPL Version 2 licenses.
  6. http://code.google.com/p/dynatree/wiki/LicenseInfo
  7. A current version and some documentation is available at
  8. http://dynatree.googlecode.com/
  9. $Version: 0.5.4$
  10. $Revision: 329, 2010-05-05 08:04:39$
  11. @depends: jquery.js
  12. @depends: ui.core.js
  13. @depends: jquery.cookie.js
  14. *************************************************************************/
  15. /*************************************************************************
  16. * Debug functions
  17. */
  18. var _canLog = true;
  19. function _log(mode, msg) {
  20. /**
  21. * Usage: logMsg("%o was toggled", this);
  22. */
  23. if( !_canLog )
  24. return;
  25. // Remove first argument
  26. var args = Array.prototype.slice.apply(arguments, [1]);
  27. // Prepend timestamp
  28. var dt = new Date();
  29. var tag = dt.getHours()+":"+dt.getMinutes()+":"+dt.getSeconds()+"."+dt.getMilliseconds();
  30. args[0] = tag + " - " + args[0];
  31. try {
  32. switch( mode ) {
  33. case "info":
  34. window.console.info.apply(window.console, args);
  35. break;
  36. case "warn":
  37. window.console.warn.apply(window.console, args);
  38. break;
  39. default:
  40. window.console.log.apply(window.console, args);
  41. }
  42. } catch(e) {
  43. if( !window.console )
  44. _canLog = false; // Permanently disable, when logging is not supported by the browser
  45. }
  46. }
  47. function logMsg(msg) {
  48. Array.prototype.unshift.apply(arguments, ["debug"]);
  49. _log.apply(this, arguments);
  50. }
  51. // Forward declaration
  52. var getDynaTreePersistData = undefined;
  53. /*************************************************************************
  54. * Constants
  55. */
  56. var DTNodeStatus_Error = -1;
  57. var DTNodeStatus_Loading = 1;
  58. var DTNodeStatus_Ok = 0;
  59. // Start of local namespace
  60. ;(function($) {
  61. /*************************************************************************
  62. * Common tool functions.
  63. */
  64. var Class = {
  65. create: function() {
  66. return function() {
  67. this.initialize.apply(this, arguments);
  68. }
  69. }
  70. }
  71. /*************************************************************************
  72. * Class DynaTreeNode
  73. */
  74. var DynaTreeNode = Class.create();
  75. DynaTreeNode.prototype = {
  76. initialize: function(parent, tree, data) {
  77. /**
  78. * @constructor
  79. */
  80. this.parent = parent;
  81. this.tree = tree;
  82. if ( typeof data == "string" )
  83. data = { title: data };
  84. if( data.key == undefined )
  85. data.key = "_" + tree._nodeCount++;
  86. this.data = $.extend({}, $.ui.dynatree.nodedatadefaults, data);
  87. this.div = null; // not yet created
  88. this.span = null; // not yet created
  89. this.childList = null; // no subnodes yet
  90. // this.isRead = false; // Lazy content not yet read
  91. this.isLoading = false; // Lazy content is being loaded
  92. this.hasSubSel = false;
  93. },
  94. toString: function() {
  95. return "dtnode<" + this.data.key + ">: '" + this.data.title + "'";
  96. },
  97. toDict: function(recursive, callback) {
  98. var dict = $.extend({}, this.data);
  99. dict.activate = ( this.tree.activeNode === this );
  100. dict.focus = ( this.tree.focusNode === this );
  101. dict.expand = this.bExpanded;
  102. dict.select = this.bSelected;
  103. if( callback )
  104. callback(dict);
  105. if( recursive && this.childList ) {
  106. dict.children = [];
  107. for(var i=0; i<this.childList.length; i++ )
  108. dict.children.push(this.childList[i].toDict(true, callback));
  109. } else {
  110. delete dict.children;
  111. }
  112. return dict;
  113. },
  114. _getInnerHtml: function() {
  115. var opts = this.tree.options;
  116. var cache = this.tree.cache;
  117. // parent connectors
  118. var rootParent = opts.rootVisible ? null : this.tree.tnRoot;
  119. var bHideFirstExpander = (opts.rootVisible && opts.minExpandLevel>0) || opts.minExpandLevel>1;
  120. var bHideFirstConnector = opts.rootVisible || opts.minExpandLevel>0;
  121. var res = "";
  122. var p = this.parent;
  123. while( p ) {
  124. // Suppress first connector column, if visible top level is always expanded
  125. if ( bHideFirstConnector && p==rootParent )
  126. break;
  127. res = ( p.isLastSibling() ? cache.tagEmpty : cache.tagVline) + res;
  128. p = p.parent;
  129. }
  130. // connector (expanded, expandable or simple)
  131. if( bHideFirstExpander && this.parent==rootParent ) {
  132. // skip connector
  133. } else if ( this.childList || this.data.isLazy ) {
  134. res += cache.tagExpander;
  135. } else {
  136. res += cache.tagConnector;
  137. }
  138. // Checkbox mode
  139. if( opts.checkbox && this.data.hideCheckbox!=true && !this.data.isStatusNode ) {
  140. res += cache.tagCheckbox;
  141. }
  142. // folder or doctype icon
  143. if ( this.data.icon ) {
  144. res += "<img src='" + opts.imagePath + this.data.icon + "' alt='' />";
  145. } else if ( this.data.icon == false ) {
  146. // icon == false means 'no icon'
  147. } else {
  148. // icon == null means 'default icon'
  149. res += cache.tagNodeIcon;
  150. }
  151. // node name
  152. var tooltip = ( this.data && typeof this.data.tooltip == "string" ) ? " title='" + this.data.tooltip + "'" : "";
  153. res += "<a href='#' class='" + opts.classNames.title + "'" + tooltip + ">" + this.data.title + "</a>";
  154. return res;
  155. },
  156. _fixOrder: function() {
  157. /**
  158. * Make sure, that <div> order matches childList order.
  159. */
  160. var cl = this.childList;
  161. if( !cl )
  162. return;
  163. var childDiv = this.div.firstChild.nextSibling;
  164. for(var i=0; i<cl.length-1; i++) {
  165. var childNode1 = cl[i];
  166. var childNode2 = childDiv.firstChild.dtnode;
  167. if( childNode1 !== childNode2 ) {
  168. this.tree.logDebug("_fixOrder: mismatch at index " + i + ": " + childNode1 + " != " + childNode2);
  169. this.div.insertBefore(childNode1.div, childNode2.div);
  170. } else {
  171. childDiv = childDiv.nextSibling;
  172. }
  173. }
  174. },
  175. render: function(bDeep, bHidden) {
  176. /**
  177. * Create HTML markup for this node.
  178. *
  179. * <div> // This div contains the node's span and list of child div's.
  180. * <span id='key'>S S S A</span> // Span contains graphic spans and title <a> tag
  181. * <div>child1</div>
  182. * <div>child2</div>
  183. * </div>
  184. */
  185. // this.tree.logDebug("%o.render()", this);
  186. var opts = this.tree.options;
  187. var cn = opts.classNames;
  188. var isLastSib = this.isLastSibling();
  189. // ---
  190. if( ! this.div ) {
  191. this.span = document.createElement("span");
  192. this.span.dtnode = this;
  193. if( this.data.key )
  194. this.span.id = this.tree.options.idPrefix + this.data.key;
  195. this.div = document.createElement("div");
  196. this.div.appendChild(this.span);
  197. if ( this.parent ) {
  198. this.parent.div.appendChild(this.div);
  199. }
  200. if( this.parent==null && !this.tree.options.rootVisible )
  201. this.span.style.display = "none";
  202. }
  203. // set node connector images, links and text
  204. this.span.innerHTML = this._getInnerHtml();
  205. // hide this node, if parent is collapsed
  206. this.div.style.display = ( this.parent==null || this.parent.bExpanded ? "" : "none");
  207. // Set classes for current status
  208. var cnList = [];
  209. cnList.push( ( this.data.isFolder ) ? cn.folder : cn.document );
  210. if( this.bExpanded )
  211. cnList.push(cn.expanded);
  212. if( this.childList != null )
  213. cnList.push(cn.hasChildren);
  214. if( this.data.isLazy && this.childList==null )
  215. cnList.push(cn.lazy);
  216. if( isLastSib )
  217. cnList.push(cn.lastsib);
  218. if( this.bSelected )
  219. cnList.push(cn.selected);
  220. if( this.hasSubSel )
  221. cnList.push(cn.partsel);
  222. if( this.tree.activeNode === this )
  223. cnList.push(cn.active);
  224. if( this.data.addClass )
  225. cnList.push(this.data.addClass);
  226. // IE6 doesn't correctly evaluate multiple class names,
  227. // so we create combined class names that can be used in the CSS
  228. cnList.push(cn.combinedExpanderPrefix
  229. + (this.bExpanded ? "e" : "c")
  230. + (this.data.isLazy && this.childList==null ? "d" : "")
  231. + (isLastSib ? "l" : "")
  232. );
  233. cnList.push(cn.combinedIconPrefix
  234. + (this.bExpanded ? "e" : "c")
  235. + (this.data.isFolder ? "f" : "")
  236. );
  237. this.span.className = cnList.join(" ");
  238. if( bDeep && this.childList && (bHidden || this.bExpanded) ) {
  239. for(var i=0; i<this.childList.length; i++) {
  240. this.childList[i].render(bDeep, bHidden)
  241. }
  242. this._fixOrder();
  243. }
  244. },
  245. hasChildren: function() {
  246. return this.childList != null;
  247. },
  248. isLastSibling: function() {
  249. var p = this.parent;
  250. if ( !p ) return true;
  251. return p.childList[p.childList.length-1] === this;
  252. },
  253. prevSibling: function() {
  254. if( !this.parent ) return null;
  255. var ac = this.parent.childList;
  256. for(var i=1; i<ac.length; i++) // start with 1, so prev(first) = null
  257. if( ac[i] === this )
  258. return ac[i-1];
  259. return null;
  260. },
  261. nextSibling: function() {
  262. if( !this.parent ) return null;
  263. var ac = this.parent.childList;
  264. for(var i=0; i<ac.length-1; i++) // up to length-2, so next(last) = null
  265. if( ac[i] === this )
  266. return ac[i+1];
  267. return null;
  268. },
  269. _setStatusNode: function(data) {
  270. // Create, modify or remove the status child node (pass 'null', to remove it).
  271. var firstChild = ( this.childList ? this.childList[0] : null );
  272. if( !data ) {
  273. if ( firstChild ) {
  274. this.div.removeChild(firstChild.div);
  275. if( this.childList.length == 1 )
  276. this.childList = null;
  277. else
  278. this.childList.shift();
  279. }
  280. } else if ( firstChild ) {
  281. data.isStatusNode = true;
  282. firstChild.data = data;
  283. firstChild.render(false, false);
  284. } else {
  285. data.isStatusNode = true;
  286. firstChild = this.addChild(data);
  287. }
  288. },
  289. setLazyNodeStatus: function(lts, opts) {
  290. var tooltip = (opts && opts.tooltip) ? opts.tooltip : null;
  291. var info = (opts && opts.info) ? " (" + opts.info + ")" : "";
  292. switch( lts ) {
  293. case DTNodeStatus_Ok:
  294. this._setStatusNode(null);
  295. // this.isRead = true;
  296. this.isLoading = false;
  297. this.render(false, false);
  298. if( this.tree.options.autoFocus ) {
  299. if( this === this.tree.tnRoot && !this.tree.options.rootVisible && this.childList ) {
  300. // special case: using ajaxInit
  301. this.childList[0].focus();
  302. } else {
  303. this.focus();
  304. }
  305. }
  306. break;
  307. case DTNodeStatus_Loading:
  308. this.isLoading = true;
  309. this._setStatusNode({
  310. title: this.tree.options.strings.loading + info,
  311. tooltip: tooltip,
  312. addClass: this.tree.options.classNames.nodeWait
  313. });
  314. break;
  315. case DTNodeStatus_Error:
  316. this.isLoading = false;
  317. this._setStatusNode({
  318. title: this.tree.options.strings.loadError + info,
  319. tooltip: tooltip,
  320. addClass: this.tree.options.classNames.nodeError
  321. });
  322. break;
  323. default:
  324. throw "Bad LazyNodeStatus: '" + lts + "'.";
  325. }
  326. },
  327. _parentList: function(includeRoot, includeSelf) {
  328. var l = [];
  329. var dtn = includeSelf ? this : this.parent;
  330. while( dtn ) {
  331. if( includeRoot || dtn.parent )
  332. l.unshift(dtn);
  333. dtn = dtn.parent;
  334. };
  335. return l;
  336. },
  337. getLevel: function() {
  338. var level = 0;
  339. var dtn = this.parent;
  340. while( dtn ) {
  341. level++;
  342. dtn = dtn.parent;
  343. };
  344. return level;
  345. },
  346. _getTypeForOuterNodeEvent: function(event) {
  347. /** Return the inner node span (title, checkbox or expander) if
  348. * event.target points to the outer span.
  349. * This function should fix issue #93:
  350. * FF2 ignores empty spans, when generating events (returning the parent instead).
  351. */
  352. var cns = this.tree.options.classNames;
  353. var target = event.target;
  354. // Only process clicks on an outer node span (probably due to a FF2 event handling bug)
  355. if( target.className.indexOf(cns.folder)<0
  356. && target.className.indexOf(cns.document)<0 ) {
  357. return null
  358. }
  359. // Event coordinates, relative to outer node span:
  360. var eventX = event.pageX - target.offsetLeft;
  361. var eventY = event.pageY - target.offsetTop;
  362. for(var i=0; i<target.childNodes.length; i++) {
  363. var cn = target.childNodes[i];
  364. var x = cn.offsetLeft - target.offsetLeft;
  365. var y = cn.offsetTop - target.offsetTop;
  366. var nx = cn.clientWidth, ny = cn.clientHeight;
  367. // alert (cn.className + ": " + x + ", " + y + ", s:" + nx + ", " + ny);
  368. if( eventX>=x && eventX<=(x+nx) && eventY>=y && eventY<=(y+ny) ) {
  369. // alert("HIT "+ cn.className);
  370. if( cn.className==cns.title )
  371. return "title";
  372. else if( cn.className==cns.expander )
  373. return "expander";
  374. else if( cn.className==cns.checkbox )
  375. return "checkbox";
  376. else if( cn.className==cns.nodeIcon )
  377. return "icon";
  378. }
  379. }
  380. return "prefix";
  381. },
  382. getEventTargetType: function(event) {
  383. // Return the part of a node, that a click event occured on.
  384. // Note: there is no check, if the was fired on TIHS node.
  385. var tcn = event && event.target ? event.target.className : "";
  386. var cns = this.tree.options.classNames;
  387. if( tcn == cns.title )
  388. return "title";
  389. else if( tcn==cns.expander )
  390. return "expander";
  391. else if( tcn==cns.checkbox )
  392. return "checkbox";
  393. else if( tcn==cns.nodeIcon )
  394. return "icon";
  395. else if( tcn==cns.empty || tcn==cns.vline || tcn==cns.connector )
  396. return "prefix";
  397. else if( tcn.indexOf(cns.folder)>=0 || tcn.indexOf(cns.document)>=0 )
  398. // FIX issue #93
  399. return this._getTypeForOuterNodeEvent(event);
  400. return null;
  401. },
  402. isVisible: function() {
  403. // Return true, if all parents are expanded.
  404. var parents = this._parentList(true, false);
  405. for(var i=0; i<parents.length; i++)
  406. if( ! parents[i].bExpanded ) return false;
  407. return true;
  408. },
  409. makeVisible: function() {
  410. // Make sure, all parents are expanded
  411. var parents = this._parentList(true, false);
  412. for(var i=0; i<parents.length; i++)
  413. parents[i]._expand(true);
  414. },
  415. focus: function() {
  416. // TODO: check, if we already have focus
  417. // this.tree.logDebug("dtnode.focus(): %o", this);
  418. this.makeVisible();
  419. try {
  420. $(this.span).find(">a").focus();
  421. } catch(e) { }
  422. },
  423. _activate: function(flag, fireEvents) {
  424. // (De)Activate - but not focus - this node.
  425. this.tree.logDebug("dtnode._activate(%o, fireEvents=%o) - %o", flag, fireEvents, this);
  426. var opts = this.tree.options;
  427. if( this.data.isStatusNode )
  428. return;
  429. if ( fireEvents && opts.onQueryActivate && opts.onQueryActivate.call(this.span, flag, this) == false )
  430. return; // Callback returned false
  431. if( flag ) {
  432. // Activate
  433. if( this.tree.activeNode ) {
  434. if( this.tree.activeNode === this )
  435. return;
  436. this.tree.activeNode.deactivate();
  437. }
  438. if( opts.activeVisible )
  439. this.makeVisible();
  440. this.tree.activeNode = this;
  441. if( opts.persist )
  442. $.cookie(opts.cookieId+"-active", this.data.key, opts.cookie);
  443. this.tree.persistence.activeKey = this.data.key;
  444. $(this.span).addClass(opts.classNames.active);
  445. if ( fireEvents && opts.onActivate ) // Pass element as 'this' (jQuery convention)
  446. opts.onActivate.call(this.span, this);
  447. } else {
  448. // Deactivate
  449. if( this.tree.activeNode === this ) {
  450. var opts = this.tree.options;
  451. if ( opts.onQueryActivate && opts.onQueryActivate.call(this.span, false, this) == false )
  452. return; // Callback returned false
  453. $(this.span).removeClass(opts.classNames.active);
  454. if( opts.persist ) {
  455. // Note: we don't pass null, but ''. So the cookie is not deleted.
  456. // If we pass null, we also have to pass a COPY of opts, because $cookie will override opts.expires (issue 84)
  457. $.cookie(opts.cookieId+"-active", "", opts.cookie);
  458. }
  459. this.tree.persistence.activeKey = null;
  460. this.tree.activeNode = null;
  461. if ( fireEvents && opts.onDeactivate )
  462. opts.onDeactivate.call(this.span, this);
  463. }
  464. }
  465. },
  466. activate: function() {
  467. // Select - but not focus - this node.
  468. // this.tree.logDebug("dtnode.activate(): %o", this);
  469. this._activate(true, true);
  470. },
  471. deactivate: function() {
  472. // this.tree.logDebug("dtnode.deactivate(): %o", this);
  473. this._activate(false, true);
  474. },
  475. isActive: function() {
  476. return (this.tree.activeNode === this);
  477. },
  478. _userActivate: function() {
  479. // Handle user click / [space] / [enter], according to clickFolderMode.
  480. var activate = true;
  481. var expand = false;
  482. if ( this.data.isFolder ) {
  483. switch( this.tree.options.clickFolderMode ) {
  484. case 2:
  485. activate = false;
  486. expand = true;
  487. break;
  488. case 3:
  489. activate = expand = true;
  490. break;
  491. }
  492. }
  493. if( this.parent == null && this.tree.options.minExpandLevel>0 ) {
  494. expand = false;
  495. }
  496. if( expand ) {
  497. this.toggleExpand();
  498. this.focus();
  499. }
  500. if( activate ) {
  501. this.activate();
  502. }
  503. },
  504. _setSubSel: function(hasSubSel) {
  505. if( hasSubSel ) {
  506. this.hasSubSel = true;
  507. $(this.span).addClass(this.tree.options.classNames.partsel);
  508. } else {
  509. this.hasSubSel = false;
  510. $(this.span).removeClass(this.tree.options.classNames.partsel);
  511. }
  512. },
  513. _fixSelectionState: function() {
  514. // fix selection status, for multi-hier mode
  515. // this.tree.logDebug("_fixSelectionState(%o) - %o", this.bSelected, this);
  516. if( this.bSelected ) {
  517. // Select all children
  518. this.visit(function(dtnode){
  519. dtnode.parent._setSubSel(true);
  520. dtnode._select(true, false, false);
  521. });
  522. // Select parents, if all children are selected
  523. var p = this.parent;
  524. while( p ) {
  525. p._setSubSel(true);
  526. var allChildsSelected = true;
  527. for(var i=0; i<p.childList.length; i++) {
  528. var n = p.childList[i];
  529. if( !n.bSelected && !n.data.isStatusNode ) {
  530. allChildsSelected = false;
  531. break;
  532. }
  533. }
  534. if( allChildsSelected )
  535. p._select(true, false, false);
  536. p = p.parent;
  537. }
  538. } else {
  539. // Deselect all children
  540. this._setSubSel(false);
  541. this.visit(function(dtnode){
  542. dtnode._setSubSel(false);
  543. dtnode._select(false, false, false);
  544. });
  545. // Deselect parents, and recalc hasSubSel
  546. var p = this.parent;
  547. while( p ) {
  548. p._select(false, false, false);
  549. var isPartSel = false;
  550. for(var i=0; i<p.childList.length; i++) {
  551. if( p.childList[i].bSelected || p.childList[i].hasSubSel ) {
  552. isPartSel = true;
  553. break;
  554. }
  555. }
  556. p._setSubSel(isPartSel);
  557. p = p.parent;
  558. }
  559. }
  560. },
  561. _select: function(sel, fireEvents, deep) {
  562. // Select - but not focus - this node.
  563. // this.tree.logDebug("dtnode._select(%o) - %o", sel, this);
  564. var opts = this.tree.options;
  565. if( this.data.isStatusNode )
  566. return;
  567. //
  568. if( this.bSelected == sel ) {
  569. // this.tree.logDebug("dtnode._select(%o) IGNORED - %o", sel, this);
  570. return;
  571. }
  572. // Allow event listener to abort selection
  573. if ( fireEvents && opts.onQuerySelect && opts.onQuerySelect.call(this.span, sel, this) == false )
  574. return; // Callback returned false
  575. // Force single-selection
  576. if( opts.selectMode==1 && sel ) {
  577. this.tree.visit(function(dtnode){
  578. if( dtnode.bSelected ) {
  579. // Deselect; assuming that in selectMode:1 there's max. one other selected node
  580. dtnode._select(false, false, false);
  581. return false;
  582. }
  583. });
  584. }
  585. this.bSelected = sel;
  586. // this.tree._changeNodeList("select", this, sel);
  587. if( sel ) {
  588. if( opts.persist )
  589. this.tree.persistence.addSelect(this.data.key);
  590. $(this.span).addClass(opts.classNames.selected);
  591. if( deep && opts.selectMode==3 )
  592. this._fixSelectionState();
  593. if ( fireEvents && opts.onSelect )
  594. opts.onSelect.call(this.span, true, this);
  595. } else {
  596. if( opts.persist )
  597. this.tree.persistence.clearSelect(this.data.key);
  598. $(this.span).removeClass(opts.classNames.selected);
  599. if( deep && opts.selectMode==3 )
  600. this._fixSelectionState();
  601. if ( fireEvents && opts.onSelect )
  602. opts.onSelect.call(this.span, false, this);
  603. }
  604. },
  605. select: function(sel) {
  606. // Select - but not focus - this node.
  607. // this.tree.logDebug("dtnode.select(%o) - %o", sel, this);
  608. if( this.data.unselectable )
  609. return this.bSelected;
  610. return this._select(sel!=false, true, true);
  611. },
  612. toggleSelect: function() {
  613. // this.tree.logDebug("dtnode.toggleSelect() - %o", this);
  614. return this.select(!this.bSelected);
  615. },
  616. isSelected: function() {
  617. return this.bSelected;
  618. },
  619. _loadContent: function() {
  620. try {
  621. var opts = this.tree.options;
  622. this.tree.logDebug("_loadContent: start - %o", this);
  623. this.setLazyNodeStatus(DTNodeStatus_Loading);
  624. if( true == opts.onLazyRead.call(this.span, this) ) {
  625. // If function returns 'true', we assume that the loading is done:
  626. this.setLazyNodeStatus(DTNodeStatus_Ok);
  627. // Otherwise (i.e. if the loading was started as an asynchronous process)
  628. // the onLazyRead(dtnode) handler is expected to call dtnode.setLazyNodeStatus(DTNodeStatus_Ok/_Error) when done.
  629. this.tree.logDebug("_loadContent: succeeded - %o", this);
  630. }
  631. } catch(e) {
  632. // alert(e);
  633. this.setLazyNodeStatus(DTNodeStatus_Error);
  634. this.tree.logWarning("_loadContent: failed - %o", e);
  635. }
  636. },
  637. _expand: function(bExpand) {
  638. // this.tree.logDebug("dtnode._expand(%o) - %o", bExpand, this);
  639. if( this.bExpanded == bExpand ) {
  640. // this.tree.logDebug("dtnode._expand(%o) IGNORED - %o", bExpand, this);
  641. return;
  642. }
  643. var opts = this.tree.options;
  644. if( !bExpand && this.getLevel()<opts.minExpandLevel ) {
  645. this.tree.logDebug("dtnode._expand(%o) forced expand - %o", bExpand, this);
  646. return;
  647. }
  648. if ( opts.onQueryExpand && opts.onQueryExpand.call(this.span, bExpand, this) == false )
  649. return; // Callback returned false
  650. this.bExpanded = bExpand;
  651. // Persist expand state
  652. if( opts.persist ) {
  653. if( bExpand )
  654. this.tree.persistence.addExpand(this.data.key);
  655. else
  656. this.tree.persistence.clearExpand(this.data.key);
  657. }
  658. this.render(false);
  659. // Auto-collapse mode: collapse all siblings
  660. if( this.bExpanded && this.parent && opts.autoCollapse ) {
  661. var parents = this._parentList(false, true);
  662. for(var i=0; i<parents.length; i++)
  663. parents[i].collapseSiblings();
  664. }
  665. // If the currently active node is now hidden, deactivate it
  666. if( opts.activeVisible && this.tree.activeNode && ! this.tree.activeNode.isVisible() ) {
  667. this.tree.activeNode.deactivate();
  668. }
  669. // Expanding a lazy node: set 'loading...' and call callback
  670. if( bExpand && this.data.isLazy && this.childList==null && !this.isLoading ) {
  671. this._loadContent();
  672. return;
  673. }
  674. // this.tree.logDebug("_expand: start div toggle - %o", this);
  675. var fxDuration = opts.fx ? (opts.fx.duration || 200) : 0;
  676. if( this.childList ) {
  677. for(var i=0; i<this.childList.length; i++ ) {
  678. var $child = $(this.childList[i].div);
  679. if( fxDuration ) {
  680. // This is a toggle, so only do it, if not already rendered (in)visible (issue 98)
  681. if( bExpand != $child.is(':visible') )
  682. $child.animate(opts.fx, fxDuration);
  683. } else {
  684. if( bExpand )
  685. $child.show();
  686. else
  687. $child.hide(); // TODO: this seems to be slow, when called the first time for an element
  688. }
  689. }
  690. }
  691. /* issue 109: using selector filter is really SLOW.
  692. // issue 98: only toggle, if render hasn't set visibility already:
  693. var filter = ">DIV" + (bExpand ? ":hidden" : ":visible");
  694. if( opts.fx ) {
  695. var duration = opts.fx.duration || 200;
  696. // $(">DIV", this.div).animate(opts.fx, duration);
  697. $(filter, this.div).animate(opts.fx, duration);
  698. } else {
  699. $(filter, this.div).toggle();
  700. // var $d = $(">DIV", this.div);
  701. // this.tree.logDebug("_expand: got div, start toggle - %o", this);
  702. // $d.toggle();
  703. }
  704. //*/
  705. // this.tree.logDebug("_expand: end div toggle - %o", this);
  706. if ( opts.onExpand )
  707. opts.onExpand.call(this.span, bExpand, this);
  708. },
  709. expand: function(flag) {
  710. if( !this.childList && !this.data.isLazy && flag )
  711. return; // Prevent expanding empty nodes
  712. if( this.parent == null && this.tree.options.minExpandLevel>0 && !flag )
  713. return; // Prevent collapsing the root
  714. this._expand(flag);
  715. },
  716. toggleExpand: function() {
  717. this.expand(!this.bExpanded);
  718. },
  719. collapseSiblings: function() {
  720. if( this.parent == null )
  721. return;
  722. var ac = this.parent.childList;
  723. for (var i=0; i<ac.length; i++) {
  724. if ( ac[i] !== this && ac[i].bExpanded )
  725. ac[i]._expand(false);
  726. }
  727. },
  728. onClick: function(event) {
  729. // this.tree.logDebug("dtnode.onClick(" + event.type + "): dtnode:" + this + ", button:" + event.button + ", which: " + event.which);
  730. var targetType = this.getEventTargetType(event);
  731. if( targetType == "expander" ) {
  732. // Clicking the expander icon always expands/collapses
  733. this.toggleExpand();
  734. this.focus(); // issue 95
  735. } else if( targetType == "checkbox" ) {
  736. // Clicking the checkbox always (de)selects
  737. this.toggleSelect();
  738. this.focus(); // issue 95
  739. } else {
  740. this._userActivate();
  741. // Chrome and Safari don't focus the a-tag on click
  742. this.span.getElementsByTagName("a")[0].focus();
  743. }
  744. // Make sure that clicks stop, otherwise <a href='#'> jumps to the top
  745. return false;
  746. },
  747. onDblClick: function(event) {
  748. // this.tree.logDebug("dtnode.onDblClick(" + event.type + "): dtnode:" + this + ", button:" + event.button + ", which: " + event.which);
  749. },
  750. onKeydown: function(event) {
  751. // this.tree.logDebug("dtnode.onKeydown(" + event.type + "): dtnode:" + this + ", charCode:" + event.charCode + ", keyCode: " + event.keyCode + ", which: " + event.which);
  752. var handled = true;
  753. // alert("keyDown" + event.which);
  754. switch( event.which ) {
  755. // charCodes:
  756. // case 43: // '+'
  757. case 107: // '+'
  758. case 187: // '+' @ Chrome, Safari
  759. if( !this.bExpanded ) this.toggleExpand();
  760. break;
  761. // case 45: // '-'
  762. case 109: // '-'
  763. case 189: // '+' @ Chrome, Safari
  764. if( this.bExpanded ) this.toggleExpand();
  765. break;
  766. //~ case 42: // '*'
  767. //~ break;
  768. //~ case 47: // '/'
  769. //~ break;
  770. // case 13: // <enter>
  771. // <enter> on a focused <a> tag seems to generate a click-event.
  772. // this._userActivate();
  773. // break;
  774. case 32: // <space>
  775. this._userActivate();
  776. break;
  777. case 8: // <backspace>
  778. if( this.parent )
  779. this.parent.focus();
  780. break;
  781. case 37: // <left>
  782. if( this.bExpanded ) {
  783. this.toggleExpand();
  784. this.focus();
  785. } else if( this.parent && (this.tree.options.rootVisible || this.parent.parent) ) {
  786. this.parent.focus();
  787. }
  788. break;
  789. case 39: // <right>
  790. if( !this.bExpanded && (this.childList || this.data.isLazy) ) {
  791. this.toggleExpand();
  792. this.focus();
  793. } else if( this.childList ) {
  794. this.childList[0].focus();
  795. }
  796. break;
  797. case 38: // <up>
  798. var sib = this.prevSibling();
  799. while( sib && sib.bExpanded && sib.childList )
  800. sib = sib.childList[sib.childList.length-1];
  801. if( !sib && this.parent && (this.tree.options.rootVisible || this.parent.parent) )
  802. sib = this.parent;
  803. if( sib ) sib.focus();
  804. break;
  805. case 40: // <down>
  806. var sib;
  807. if( this.bExpanded && this.childList ) {
  808. sib = this.childList[0];
  809. } else {
  810. var parents = this._parentList(false, true);
  811. for(var i=parents.length-1; i>=0; i--) {
  812. sib = parents[i].nextSibling();
  813. if( sib ) break;
  814. }
  815. }
  816. if( sib ) sib.focus();
  817. break;
  818. default:
  819. handled = false;
  820. }
  821. // Return false, if handled, to prevent default processing
  822. return !handled;
  823. },
  824. onKeypress: function(event) {
  825. // onKeypress is only hooked to allow user callbacks.
  826. // We don't process it, because IE and Safari don't fire keypress for cursor keys.
  827. // this.tree.logDebug("dtnode.onKeypress(" + event.type + "): dtnode:" + this + ", charCode:" + event.charCode + ", keyCode: " + event.keyCode + ", which: " + event.which);
  828. },
  829. onFocus: function(event) {
  830. // Handles blur and focus events.
  831. // this.tree.logDebug("dtnode.onFocus(%o): %o", event, this);
  832. var opts = this.tree.options;
  833. if ( event.type=="blur" || event.type=="focusout" ) {
  834. if ( opts.onBlur ) // Pass element as 'this' (jQuery convention)
  835. opts.onBlur.call(this.span, this);
  836. if( this.tree.tnFocused )
  837. $(this.tree.tnFocused.span).removeClass(opts.classNames.focused);
  838. this.tree.tnFocused = null;
  839. if( opts.persist )
  840. $.cookie(opts.cookieId+"-focus", "", opts.cookie);
  841. } else if ( event.type=="focus" || event.type=="focusin") {
  842. // Fix: sometimes the blur event is not generated
  843. if( this.tree.tnFocused && this.tree.tnFocused !== this ) {
  844. this.tree.logDebug("dtnode.onFocus: out of sync: curFocus: %o", this.tree.tnFocused);
  845. $(this.tree.tnFocused.span).removeClass(opts.classNames.focused);
  846. }
  847. this.tree.tnFocused = this;
  848. if ( opts.onFocus ) // Pass element as 'this' (jQuery convention)
  849. opts.onFocus.call(this.span, this);
  850. $(this.tree.tnFocused.span).addClass(opts.classNames.focused);
  851. if( opts.persist )
  852. $.cookie(opts.cookieId+"-focus", this.data.key, opts.cookie);
  853. }
  854. // TODO: return anything?
  855. // return false;
  856. },
  857. visit: function(fn, data, includeSelf) {
  858. // Call fn(dtnode, data) for all child nodes. Stop iteration, if fn() returns false.
  859. var n = 0;
  860. if( includeSelf == true ) {
  861. if( fn(this, data) == false )
  862. return 1;
  863. n++;
  864. }
  865. if ( this.childList )
  866. for (var i=0; i<this.childList.length; i++)
  867. n += this.childList[i].visit(fn, data, true);
  868. return n;
  869. },
  870. remove: function() {
  871. // Remove this node
  872. // this.tree.logDebug ("%o.remove()", this);
  873. if ( this === this.tree.root )
  874. return false;
  875. return this.parent.removeChild(this);
  876. },
  877. removeChild: function(tn) {
  878. // Remove tn from list of direct children.
  879. var ac = this.childList;
  880. if( ac.length == 1 ) {
  881. if( tn !== ac[0] )
  882. throw "removeChild: invalid child";
  883. return this.removeChildren();
  884. }
  885. if( tn === this.tree.activeNode )
  886. tn.deactivate();
  887. if( this.tree.options.persist ) {
  888. if( tn.bSelected )
  889. this.tree.persistence.clearSelect(tn.data.key);
  890. if ( tn.bExpanded )
  891. this.tree.persistence.clearExpand(tn.data.key);
  892. }
  893. tn.removeChildren(true);
  894. this.div.removeChild(tn.div);
  895. for(var i=0; i<ac.length; i++) {
  896. if( ac[i] === tn ) {
  897. this.childList.splice(i, 1);
  898. delete tn;
  899. break;
  900. }
  901. }
  902. },
  903. removeChildren: function(isRecursiveCall, retainPersistence) {
  904. // Remove all child nodes (more efficiently than recursive remove())
  905. // this.tree.logDebug ("%o.removeChildren(%o)", this, isRecursiveCall);
  906. var tree = this.tree;
  907. var ac = this.childList;
  908. if( ac ) {
  909. for(var i=0; i<ac.length; i++) {
  910. var tn=ac[i];
  911. // this.tree.logDebug ("del %o", tn);
  912. if ( tn === tree.activeNode && !retainPersistence )
  913. tn.deactivate();
  914. if( this.tree.options.persist && !retainPersistence ) {
  915. if( tn.bSelected )
  916. this.tree.persistence.clearSelect(tn.data.key);
  917. if ( tn.bExpanded )
  918. this.tree.persistence.clearExpand(tn.data.key);
  919. }
  920. tn.removeChildren(true, retainPersistence);
  921. this.div.removeChild(tn.div);
  922. delete tn;
  923. }
  924. this.childList = null;
  925. }
  926. if( ! isRecursiveCall ) {
  927. // this._expand(false);
  928. // this.isRead = false;
  929. this.isLoading = false;
  930. this.render(false, false);
  931. }
  932. },
  933. reload: function(force) {
  934. // Discard lazy content (and reload, if node was expanded).
  935. if( this.parent == null )
  936. return this.tree.reload();
  937. if( ! this.data.isLazy )
  938. throw "node.reload() requires lazy nodes.";
  939. if( this.bExpanded ) {
  940. this.expand(false);
  941. this.removeChildren();
  942. this.expand(true);
  943. } else {
  944. this.removeChildren();
  945. if( force )
  946. this._loadContent();
  947. }
  948. },
  949. _addChildNode: function(dtnode, beforeNode) {
  950. /**
  951. * Internal function to add one single DynatreeNode as a child.
  952. *
  953. */
  954. var tree = this.tree;
  955. var opts = tree.options;
  956. var pers = tree.persistence;
  957. // tree.logDebug("%o._addChildNode(%o)", this, dtnode);
  958. // --- Update and fix dtnode attributes if necessary
  959. dtnode.parent = this;
  960. // if( beforeNode && (beforeNode.parent !== this || beforeNode === dtnode ) )
  961. // throw "<beforeNode> must be another child of <this>";
  962. // --- Add dtnode as a child
  963. if ( this.childList==null ) {
  964. this.childList = [];
  965. } else if( ! beforeNode ) {
  966. // Fix 'lastsib'
  967. $(this.childList[this.childList.length-1].span).removeClass(opts.classNames.lastsib);
  968. }
  969. if( beforeNode ) {
  970. var iBefore = $.inArray(beforeNode, this.childList);
  971. if( iBefore < 0 )
  972. throw "<beforeNode> must be a child of <this>";
  973. this.childList.splice(iBefore, 0, dtnode);
  974. // alert(this.childList);
  975. } else {
  976. // Append node
  977. this.childList.push(dtnode);
  978. }
  979. // --- Handle persistence
  980. // Initial status is read from cookies, if persistence is active and
  981. // cookies are already present.
  982. // Otherwise the status is read from the data attributes and then persisted.
  983. var isInitializing = tree.isInitializing();
  984. if( opts.persist && pers.cookiesFound && isInitializing ) {
  985. // Init status from cookies
  986. // tree.logDebug("init from cookie, pa=%o, dk=%o", pers.activeKey, dtnode.data.key);
  987. if( pers.activeKey == dtnode.data.key )
  988. tree.activeNode = dtnode;
  989. if( pers.focusedKey == dtnode.data.key )
  990. tree.focusNode = dtnode;
  991. dtnode.bExpanded = ($.inArray(dtnode.data.key, pers.expandedKeyList) >= 0);
  992. dtnode.bSelected = ($.inArray(dtnode.data.key, pers.selectedKeyList) >= 0);
  993. // tree.logDebug(" key=%o, bSelected=%o", dtnode.data.key, dtnode.bSelected);
  994. } else {
  995. // Init status from data (Note: we write the cookies after the init phase)
  996. // tree.logDebug("init from data");
  997. if( dtnode.data.activate ) {
  998. tree.activeNode = dtnode;
  999. if( opts.persist )
  1000. pers.activeKey = dtnode.data.key;
  1001. }
  1002. if( dtnode.data.focus ) {
  1003. tree.focusNode = dtnode;
  1004. if( opts.persist )
  1005. pers.focusedKey = dtnode.data.key;
  1006. }
  1007. dtnode.bExpanded = ( dtnode.data.expand == true ); // Collapsed by default
  1008. if( dtnode.bExpanded && opts.persist )
  1009. pers.addExpand(dtnode.data.key);
  1010. dtnode.bSelected = ( dtnode.data.select == true ); // Deselected by default
  1011. /*
  1012. Doesn't work, cause pers.selectedKeyList may be null
  1013. if( dtnode.bSelected && opts.selectMode==1
  1014. && pers.selectedKeyList && pers.selectedKeyList.length>0 ) {
  1015. tree.logWarning("Ignored multi-selection in single-mode for %o", dtnode);
  1016. dtnode.bSelected = false; // Fixing bad input data (multi selection for mode:1)
  1017. }
  1018. */
  1019. if( dtnode.bSelected && opts.persist )
  1020. pers.addSelect(dtnode.data.key);
  1021. }
  1022. // Always expand, if it's below minExpandLevel
  1023. // tree.logDebug ("%o._addChildNode(%o), l=%o", this, dtnode, dtnode.getLevel());
  1024. if ( opts.minExpandLevel >= dtnode.getLevel() ) {
  1025. // tree.logDebug ("Force expand for %o", dtnode);
  1026. this.bExpanded = true;
  1027. }
  1028. // In multi-hier mode, update the parents selection state
  1029. // issue #82: only if not initializing, because the children may not exist yet
  1030. // if( !dtnode.data.isStatusNode && opts.selectMode==3 && !isInitializing )
  1031. // dtnode._fixSelectionState();
  1032. // In multi-hier mode, update the parents selection state
  1033. if( dtnode.bSelected && opts.selectMode==3 ) {
  1034. var p = this;
  1035. while( p ) {
  1036. if( !p.hasSubSel )
  1037. p._setSubSel(true);
  1038. p = p.parent;
  1039. }
  1040. }
  1041. // render this node and the new child
  1042. if ( tree.bEnableUpdate )
  1043. this.render(true, true);
  1044. return dtnode;
  1045. },
  1046. addChild: function(obj, beforeNode) {
  1047. /**
  1048. * Add a node object as child.
  1049. *
  1050. * This should be the only place, where a DynaTreeNode is constructed!
  1051. * (Except for the root node creation in the tree constructor)
  1052. *
  1053. * @param obj A JS object (may be recursive) or an array of those.
  1054. * @param {DynaTreeNode} beforeNode (optional) sibling node.
  1055. *
  1056. * Data format: array of node objects, with optional 'children' attributes.
  1057. * [
  1058. * { title: "t1", isFolder: true, ... }
  1059. * { title: "t2", isFolder: true, ...,
  1060. * children: [
  1061. * {title: "t2.1", ..},
  1062. * {..}
  1063. * ]
  1064. * }
  1065. * ]
  1066. * A simple object is also accepted instead of an array.
  1067. *
  1068. */
  1069. // this.tree.logDebug("%o.addChild(%o, %o)", this, obj, beforeNode);
  1070. if( !obj || obj.length==0 ) // Passed null or undefined or empty array
  1071. return;
  1072. if( obj instanceof DynaTreeNode )
  1073. return this._addChildNode(obj, beforeNode);
  1074. if( !obj.length ) // Passed a single data object
  1075. obj = [ obj ];
  1076. var prevFlag = this.tree.enableUpdate(false);
  1077. var tnFirst = null;
  1078. for (var i=0; i<obj.length; i++) {
  1079. var data = obj[i];
  1080. var dtnode = this._addChildNode(new DynaTreeNode(this, this.tree, data), beforeNode);
  1081. if( !tnFirst ) tnFirst = dtnode;
  1082. // Add child nodes recursively
  1083. if( data.children )
  1084. dtnode.addChild(data.children, null);
  1085. }
  1086. this.tree.enableUpdate(prevFlag);
  1087. return tnFirst;
  1088. },
  1089. append: function(obj) {
  1090. this.tree.logWarning("node.append() is deprecated (use node.addChild() instead).");
  1091. return this.addChild(obj, null);
  1092. },
  1093. appendAjax: function(ajaxOptions) {
  1094. this.removeChildren(false, true);
  1095. this.setLazyNodeStatus(DTNodeStatus_Loading);
  1096. // Ajax option inheritance: $.ajaxSetup < $.ui.dynatree.defaults.ajaxDefaults < tree.options.ajaxDefaults < ajaxOptions
  1097. var self = this;
  1098. var orgSuccess = ajaxOptions.success;
  1099. var orgError = ajaxOptions.error;
  1100. var options = $.extend({}, this.tree.options.ajaxDefaults, ajaxOptions, {
  1101. /*
  1102. complete: function(req, textStatus){
  1103. alert("ajax complete");
  1104. },
  1105. timeout: 5000, // 5 sec
  1106. */
  1107. success: function(data, textStatus){
  1108. // <this> is the request options
  1109. // self.tree.logDebug("appendAjax().success");
  1110. var prevPhase = self.tree.phase;
  1111. self.tree.phase = "init";
  1112. // self.append(data);
  1113. self.addChild(data, null);
  1114. self.tree.phase = "postInit";
  1115. self.setLazyNodeStatus(DTNodeStatus_Ok);
  1116. if( orgSuccess )
  1117. orgSuccess.call(options, self);
  1118. self.tree.phase = prevPhase;
  1119. },
  1120. error: function(XMLHttpRequest, textStatus, errorThrown){
  1121. // <this> is the request options
  1122. // self.tree.logWarning("appendAjax failed: %o:\n%o\n%o", textStatus, XMLHttpRequest, errorThrown);
  1123. self.tree.logWarning("appendAjax failed:", textStatus, ":\n", XMLHttpRequest, "\n", errorThrown);
  1124. self.setLazyNodeStatus(DTNodeStatus_Error, {info: textStatus, tooltip: ""+errorThrown});
  1125. if( orgError )
  1126. orgError.call(options, self, XMLHttpRequest, textStatus, errorThrown);
  1127. }
  1128. });
  1129. $.ajax(options);
  1130. },
  1131. // --- end of class
  1132. lastentry: undefined
  1133. }
  1134. /*************************************************************************
  1135. * class DynaTreeStatus
  1136. */
  1137. var DynaTreeStatus = Class.create();
  1138. DynaTreeStatus._getTreePersistData = function(cookieId, cookieOpts) {
  1139. // Static member: Return persistence information from cookies
  1140. var ts = new DynaTreeStatus(cookieId, cookieOpts);
  1141. ts.read();
  1142. return ts.toDict();
  1143. }
  1144. // Make available in global scope
  1145. getDynaTreePersistData = DynaTreeStatus._getTreePersistData;
  1146. DynaTreeStatus.prototype = {
  1147. // Constructor
  1148. initialize: function(cookieId, cookieOpts) {
  1149. this._log("DynaTreeStatus: initialize");
  1150. if( cookieId === undefined )
  1151. cookieId = $.ui.dynatree.defaults.cookieId;
  1152. cookieOpts = $.extend({}, $.ui.dynatree.defaults.cookie, cookieOpts);
  1153. this.cookieId = cookieId;
  1154. this.cookieOpts = cookieOpts;
  1155. this.cookiesFound = undefined;
  1156. this.activeKey = null;
  1157. this.focusedKey = null;
  1158. this.expandedKeyList = null;
  1159. this.selectedKeyList = null;
  1160. },
  1161. // member functions
  1162. _log: function(msg) {
  1163. // this.logDebug("_changeNodeList(%o): nodeList:%o, idx:%o", mode, nodeList, idx);
  1164. Array.prototype.unshift.apply(arguments, ["debug"]);
  1165. _log.apply(this, arguments);
  1166. },
  1167. read: function() {
  1168. this._log("DynaTreeStatus: read");
  1169. // Read or init cookies.
  1170. this.cookiesFound = false;
  1171. var cookie = $.cookie(this.cookieId + "-active");
  1172. this.activeKey = ( cookie == null ) ? "" : cookie;
  1173. if( cookie != null ) this.cookiesFound = true;
  1174. cookie = $.cookie(this.cookieId + "-focus");
  1175. this.focusedKey = ( cookie == null ) ? "" : cookie;
  1176. if( cookie != null ) this.cookiesFound = true;
  1177. cookie = $.cookie(this.cookieId + "-expand");
  1178. this.expandedKeyList = ( cookie == null ) ? [] : cookie.split(",");
  1179. if( cookie != null ) this.cookiesFound = true;
  1180. cookie = $.cookie(this.cookieId + "-select");
  1181. this.selectedKeyList = ( cookie == null ) ? [] : cookie.split(",");
  1182. if( cookie != null ) this.cookiesFound = true;
  1183. },
  1184. write: function() {
  1185. this._log("DynaTreeStatus: write");
  1186. $.cookie(this.cookieId + "-active", ( this.activeKey == null ) ? "" : this.activeKey, this.cookieOpts);
  1187. $.cookie(this.cookieId + "-focus", ( this.focusedKey == null ) ? "" : this.focusedKey, this.cookieOpts);
  1188. $.cookie(this.cookieId + "-expand", ( this.expandedKeyList == null ) ? "" : this.expandedKeyList.join(","), this.cookieOpts);
  1189. $.cookie(this.cookieId + "-select", ( this.selectedKeyList == null ) ? "" : this.selectedKeyList.join(","), this.cookieOpts);
  1190. },
  1191. addExpand: function(key) {
  1192. this._log("addExpand(%o)", key);
  1193. if( $.inArray(key, this.expandedKeyList) < 0 ) {
  1194. this.expandedKeyList.push(key);
  1195. $.cookie(this.cookieId + "-expand", this.expandedKeyList.join(","), this.cookieOpts);
  1196. }
  1197. },
  1198. clearExpand: function(key) {
  1199. this._log("clearExpand(%o)", key);
  1200. var idx = $.inArray(key, this.expandedKeyList);
  1201. if( idx >= 0 ) {
  1202. this.expandedKeyList.splice(idx, 1);
  1203. $.cookie(this.cookieId + "-expand", this.expandedKeyList.join(","), this.cookieOpts);
  1204. }
  1205. },
  1206. addSelect: function(key) {
  1207. this._log("addSelect(%o)", key);
  1208. if( $.inArray(key, this.selectedKeyList) < 0 ) {
  1209. this.selectedKeyList.push(key);
  1210. $.cookie(this.cookieId + "-select", this.selectedKeyList.join(","), this.cookieOpts);
  1211. }
  1212. },
  1213. clearSelect: function(key) {
  1214. this._log("clearSelect(%o)", key);
  1215. var idx = $.inArray(key, this.selectedKeyList);
  1216. if( idx >= 0 ) {
  1217. this.selectedKeyList.splice(idx, 1);
  1218. $.cookie(this.cookieId + "-select", this.selectedKeyList.join(","), this.cookieOpts);
  1219. }
  1220. },
  1221. isReloading: function() {
  1222. return this.cookiesFound == true;
  1223. },
  1224. toDict: function() {
  1225. return {
  1226. cookiesFound: this.cookiesFound,
  1227. activeKey: this.activeKey,
  1228. focusedKey: this.activeKey,
  1229. expandedKeyList: this.expandedKeyList,
  1230. selectedKeyList: this.selectedKeyList
  1231. };
  1232. },
  1233. // --- end of class
  1234. lastentry: undefined
  1235. };
  1236. /*************************************************************************
  1237. * class DynaTree
  1238. */
  1239. var DynaTree = Class.create();
  1240. // --- Static members ----------------------------------------------------------
  1241. DynaTree.version = "$Version: 0.5.4$";
  1242. /*
  1243. DynaTree._initTree = function() {
  1244. };
  1245. DynaTree._bind = function() {
  1246. };
  1247. */
  1248. //--- Class members ------------------------------------------------------------
  1249. DynaTree.prototype = {
  1250. // Constructor
  1251. // initialize: function(divContainer, options) {
  1252. initialize: function($widget) {
  1253. // instance members
  1254. this.phase = "init";
  1255. this.$widget = $widget;
  1256. this.options = $widget.options;
  1257. this.$tree = $widget.element;
  1258. // find container element
  1259. this.divTree = this.$tree.get(0);
  1260. },
  1261. // member functions
  1262. _load: function() {
  1263. var $widget = this.$widget;
  1264. var opts = this.options;
  1265. this.bEnableUpdate = true;
  1266. this._nodeCount = 1;
  1267. this.activeNode = null;
  1268. this.focusNode = null;
  1269. // If a 'options.classNames' dictionary was passed, still use defaults
  1270. // for undefined classes:
  1271. if( opts.classNames !== $.ui.dynatree.defaults.classNames ) {
  1272. opts.classNames = $.extend({}, $.ui.dynatree.defaults.classNames, opts.classNames);
  1273. }
  1274. // Guess skin path, if not specified
  1275. if(!opts.imagePath) {
  1276. $("script").each( function () {
  1277. // Eclipse syntax parser breaks on this expression, so put it at the bottom:
  1278. if( this.src.search(_rexDtLibName) >= 0 ) {
  1279. if( this.src.indexOf("/")>=0 ) // issue #47
  1280. opts.imagePath = this.src.slice(0, this.src.lastIndexOf("/")) + "/skin/";
  1281. else
  1282. opts.imagePath = "skin/";
  1283. // logMsg("Guessing imagePath from '%s': '%s'", this.src, opts.imagePath);
  1284. return false; // first match
  1285. }
  1286. });
  1287. }
  1288. this.persistence = new DynaTreeStatus(opts.cookieId, opts.cookie);
  1289. if( opts.persist ) {
  1290. if( !$.cookie )
  1291. _log("warn", "Please include jquery.cookie.js to use persistence.");
  1292. this.persistence.read();
  1293. }
  1294. this.logDebug("DynaTree.persistence: %o", this.persistence.toDict());
  1295. // Cached tag strings
  1296. this.cache = {
  1297. tagEmpty: "<span class='" + opts.classNames.empty + "'></span>",
  1298. tagVline: "<span class='" + opts.classNames.vline + "'></span>",
  1299. tagExpander: "<span class='" + opts.classNames.expander + "'></span>",
  1300. tagConnector: "<span class='" + opts.classNames.connector + "'></span>",
  1301. tagNodeIcon: "<span class='" + opts.classNames.nodeIcon + "'></span>",
  1302. tagCheckbox: "<span class='" + opts.classNames.checkbox + "'></span>",
  1303. lastentry: undefined
  1304. };
  1305. // Clear container, in case it contained some 'waiting' or 'error' text
  1306. // for clients that don't support JS.
  1307. // We don't do this however, if we try to load from an embedded UL element.
  1308. if( opts.children || (opts.initAjax && opts.initAjax.url) || opts.initId )
  1309. $(this.divTree).empty();
  1310. else if( this.divRoot )
  1311. $(this.divRoot).remove();
  1312. // create the root element
  1313. this.tnRoot = new DynaTreeNode(null, this, {title: opts.title, key: "root"});
  1314. this.tnRoot.data.isFolder = true;
  1315. this.tnRoot.render(false, false);
  1316. this.divRoot = this.tnRoot.div;
  1317. this.divRoot.className = opts.classNames.container;
  1318. // add root to container
  1319. // TODO: this should be delayed until all children have been created for performance reasons
  1320. this.divTree.appendChild(this.divRoot);
  1321. var root = this.tnRoot;
  1322. var isReloading = ( opts.persist && this.persistence.isReloading() );
  1323. var isLazy = false;
  1324. var prevFlag = this.enableUpdate(false);
  1325. this.logDebug("Dynatree._load(): read tree structure...");
  1326. // Init tree structure
  1327. if( opts.children ) {
  1328. // Read structure from node array
  1329. root.addChild(opts.children);
  1330. } else if( opts.initAjax && opts.initAjax.url ) {
  1331. // Init tree from AJAX request
  1332. isLazy = true;
  1333. root.data.isLazy = true;
  1334. this._reloadAjax();
  1335. } else if( opts.initId ) {
  1336. // Init tree from another UL element
  1337. this._createFromTag(root, $("#"+opts.initId));
  1338. } else {
  1339. // Init tree from the first UL element inside the container <div>
  1340. var $ul = this.$tree.find(">ul").hide();
  1341. this._createFromTag(root, $ul);
  1342. $ul.remove();
  1343. }
  1344. this._checkConsistency();
  1345. // Render html markup
  1346. this.logDebug("Dynatree._load(): render nodes...");
  1347. this.enableUpdate(prevFlag);
  1348. // bind event handlers
  1349. this.logDebug("Dynatree._load(): bind events...");
  1350. this.$widget.bind();
  1351. // --- Post-load processing
  1352. this.logDebug("Dynatree._load(): postInit...");
  1353. this.phase = "postInit";
  1354. // In persist mode, make sure that cookies are written, even if they are empty
  1355. if( opts.persist ) {
  1356. this.persistence.write();
  1357. }
  1358. // Set focus, if possible (this will also fire an event and write a cookie)
  1359. if( this.focusNode && this.focusNode.isVisible() ) {
  1360. this.logDebug("Focus on init: %o", this.focusNode);
  1361. this.focusNode.focus();
  1362. }
  1363. if( !isLazy && opts.onPostInit ) {
  1364. opts.onPostInit.call(this, isReloading, false);
  1365. }
  1366. this.phase = "idle";
  1367. },
  1368. _reloadAjax: function() {
  1369. // Reload
  1370. var opts = this.options;
  1371. if( ! opts.initAjax || ! opts.initAjax.url )
  1372. throw "tree.reload() requires 'initAjax' mode.";
  1373. var pers = this.persistence;
  1374. var ajaxOpts = $.extend({}, opts.initAjax);
  1375. // Append cookie info to the request
  1376. // this.logDebug("reloadAjax: key=%o, an.key:%o", pers.activeKey, this.activeNode?this.activeNode.data.key:"?");
  1377. if( ajaxOpts.addActiveKey )
  1378. ajaxOpts.data.activeKey = pers.activeKey;
  1379. if( ajaxOpts.addFocusedKey )
  1380. ajaxOpts.data.focusedKey = pers.focusedKey;
  1381. if( ajaxOpts.addExpandedKeyList )
  1382. ajaxOpts.data.expandedKeyList = pers.expandedKeyList.join(",");
  1383. if( ajaxOpts.addSelectedKeyList )
  1384. ajaxOpts.data.selectedKeyList = pers.selectedKeyList.join(",");
  1385. // Set up onPostInit callback to be called when Ajax returns
  1386. if( opts.onPostInit ) {
  1387. if( ajaxOpts.success )
  1388. this.logWarning("initAjax: success callback is ignored when onPostInit was specified.");
  1389. if( ajaxOpts.error )
  1390. this.logWarning("initAjax: error callback is ignored when onPostInit was specified.");
  1391. var isReloading = pers.isReloading();
  1392. ajaxOpts["success"] = function(dtnode) { opts.onPostInit.call(dtnode.tree, isReloading, false); };
  1393. ajaxOpts["error"] = function(dtnode) { opts.onPostInit.call(dtnode.tree, isReloading, true); };
  1394. }
  1395. this.logDebug("Dynatree._init(): send Ajax request...");
  1396. this.tnRoot.appendAjax(ajaxOpts);
  1397. },
  1398. toString: function() {
  1399. return "DynaTree '" + this.options.title + "'";
  1400. },
  1401. toDict: function() {
  1402. return this.tnRoot.toDict(true);
  1403. },
  1404. getPersistData: function() {
  1405. return this.persistence.toDict();
  1406. },
  1407. logDebug: function(msg) {
  1408. if( this.options.debugLevel >= 2 ) {
  1409. Array.prototype.unshift.apply(arguments, ["debug"]);
  1410. _log.apply(this, arguments);
  1411. }
  1412. },
  1413. logInfo: function(msg) {
  1414. if( this.options.debugLevel >= 1 ) {
  1415. Array.prototype.unshift.apply(arguments, ["info"]);
  1416. _log.apply(this, arguments);
  1417. }
  1418. },
  1419. logWarning: function(msg) {
  1420. Array.prototype.unshift.apply(arguments, ["warn"]);
  1421. _log.apply(this, arguments);
  1422. },
  1423. isInitializing: function() {
  1424. return ( this.phase=="init" || this.phase=="postInit" );
  1425. },
  1426. isReloading: function() {
  1427. return ( this.phase=="init" || this.phase=="postInit" ) && this.options.persist && this.persistence.cookiesFound;
  1428. },
  1429. isUserEvent: function() {
  1430. return ( this.phase=="userEvent" );
  1431. },
  1432. redraw: function() {
  1433. this.logDebug("dynatree.redraw()...");
  1434. this.tnRoot.render(true, true);
  1435. this.logDebug("dynatree.redraw() done.");
  1436. },
  1437. reloadAjax: function() {
  1438. this.logWarning("tree.reloadAjax() is deprecated since v0.5.2 (use reload() instead).");
  1439. },
  1440. reload: function() {
  1441. this._load();
  1442. },
  1443. getRoot: function() {
  1444. ret