Forked mumble-django project from https://bitbucket.org/Svedrin/mumble-django
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4707 lines
148 KiB

  1. /*!
  2. * Ext JS Library 3.2.0
  3. * Copyright(c) 2006-2010 Ext JS, Inc.
  4. * licensing@extjs.com
  5. * http://www.extjs.com/license
  6. */
  7. /**
  8. * @class Ext.tree.TreePanel
  9. * @extends Ext.Panel
  10. * <p>The TreePanel provides tree-structured UI representation of tree-structured data.</p>
  11. * <p>{@link Ext.tree.TreeNode TreeNode}s added to the TreePanel may each contain metadata
  12. * used by your application in their {@link Ext.tree.TreeNode#attributes attributes} property.</p>
  13. * <p><b>A TreePanel must have a {@link #root} node before it is rendered.</b> This may either be
  14. * specified using the {@link #root} config option, or using the {@link #setRootNode} method.
  15. * <p>An example of tree rendered to an existing div:</p><pre><code>
  16. var tree = new Ext.tree.TreePanel({
  17. renderTo: 'tree-div',
  18. useArrows: true,
  19. autoScroll: true,
  20. animate: true,
  21. enableDD: true,
  22. containerScroll: true,
  23. border: false,
  24. // auto create TreeLoader
  25. dataUrl: 'get-nodes.php',
  26. root: {
  27. nodeType: 'async',
  28. text: 'Ext JS',
  29. draggable: false,
  30. id: 'source'
  31. }
  32. });
  33. tree.getRootNode().expand();
  34. * </code></pre>
  35. * <p>The example above would work with a data packet similar to this:</p><pre><code>
  36. [{
  37. "text": "adapter",
  38. "id": "source\/adapter",
  39. "cls": "folder"
  40. }, {
  41. "text": "dd",
  42. "id": "source\/dd",
  43. "cls": "folder"
  44. }, {
  45. "text": "debug.js",
  46. "id": "source\/debug.js",
  47. "leaf": true,
  48. "cls": "file"
  49. }]
  50. * </code></pre>
  51. * <p>An example of tree within a Viewport:</p><pre><code>
  52. new Ext.Viewport({
  53. layout: 'border',
  54. items: [{
  55. region: 'west',
  56. collapsible: true,
  57. title: 'Navigation',
  58. xtype: 'treepanel',
  59. width: 200,
  60. autoScroll: true,
  61. split: true,
  62. loader: new Ext.tree.TreeLoader(),
  63. root: new Ext.tree.AsyncTreeNode({
  64. expanded: true,
  65. children: [{
  66. text: 'Menu Option 1',
  67. leaf: true
  68. }, {
  69. text: 'Menu Option 2',
  70. leaf: true
  71. }, {
  72. text: 'Menu Option 3',
  73. leaf: true
  74. }]
  75. }),
  76. rootVisible: false,
  77. listeners: {
  78. click: function(n) {
  79. Ext.Msg.alert('Navigation Tree Click', 'You clicked: "' + n.attributes.text + '"');
  80. }
  81. }
  82. }, {
  83. region: 'center',
  84. xtype: 'tabpanel',
  85. // remaining code not shown ...
  86. }]
  87. });
  88. </code></pre>
  89. *
  90. * @cfg {Ext.tree.TreeNode} root The root node for the tree.
  91. * @cfg {Boolean} rootVisible <tt>false</tt> to hide the root node (defaults to <tt>true</tt>)
  92. * @cfg {Boolean} lines <tt>false</tt> to disable tree lines (defaults to <tt>true</tt>)
  93. * @cfg {Boolean} enableDD <tt>true</tt> to enable drag and drop
  94. * @cfg {Boolean} enableDrag <tt>true</tt> to enable just drag
  95. * @cfg {Boolean} enableDrop <tt>true</tt> to enable just drop
  96. * @cfg {Object} dragConfig Custom config to pass to the {@link Ext.tree.TreeDragZone} instance
  97. * @cfg {Object} dropConfig Custom config to pass to the {@link Ext.tree.TreeDropZone} instance
  98. * @cfg {String} ddGroup The DD group this TreePanel belongs to
  99. * @cfg {Boolean} ddAppendOnly <tt>true</tt> if the tree should only allow append drops (use for trees which are sorted)
  100. * @cfg {Boolean} ddScroll <tt>true</tt> to enable body scrolling
  101. * @cfg {Boolean} containerScroll <tt>true</tt> to register this container with ScrollManager
  102. * @cfg {Boolean} hlDrop <tt>false</tt> to disable node highlight on drop (defaults to the value of {@link Ext#enableFx})
  103. * @cfg {String} hlColor The color of the node highlight (defaults to <tt>'C3DAF9'</tt>)
  104. * @cfg {Boolean} animate <tt>true</tt> to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx})
  105. * @cfg {Boolean} singleExpand <tt>true</tt> if only 1 node per branch may be expanded
  106. * @cfg {Object} selModel A tree selection model to use with this TreePanel (defaults to an {@link Ext.tree.DefaultSelectionModel})
  107. * @cfg {Boolean} trackMouseOver <tt>false</tt> to disable mouse over highlighting
  108. * @cfg {Ext.tree.TreeLoader} loader A {@link Ext.tree.TreeLoader} for use with this TreePanel
  109. * @cfg {String} pathSeparator The token used to separate sub-paths in path strings (defaults to <tt>'/'</tt>)
  110. * @cfg {Boolean} useArrows <tt>true</tt> to use Vista-style arrows in the tree (defaults to <tt>false</tt>)
  111. * @cfg {String} requestMethod The HTTP request method for loading data (defaults to the value of {@link Ext.Ajax#method}).
  112. *
  113. * @constructor
  114. * @param {Object} config
  115. * @xtype treepanel
  116. */
  117. Ext.tree.TreePanel = Ext.extend(Ext.Panel, {
  118. rootVisible : true,
  119. animate : Ext.enableFx,
  120. lines : true,
  121. enableDD : false,
  122. hlDrop : Ext.enableFx,
  123. pathSeparator : '/',
  124. /**
  125. * @cfg {Array} bubbleEvents
  126. * <p>An array of events that, when fired, should be bubbled to any parent container.
  127. * See {@link Ext.util.Observable#enableBubble}.
  128. * Defaults to <tt>[]</tt>.
  129. */
  130. bubbleEvents : [],
  131. initComponent : function(){
  132. Ext.tree.TreePanel.superclass.initComponent.call(this);
  133. if(!this.eventModel){
  134. this.eventModel = new Ext.tree.TreeEventModel(this);
  135. }
  136. // initialize the loader
  137. var l = this.loader;
  138. if(!l){
  139. l = new Ext.tree.TreeLoader({
  140. dataUrl: this.dataUrl,
  141. requestMethod: this.requestMethod
  142. });
  143. }else if(Ext.isObject(l) && !l.load){
  144. l = new Ext.tree.TreeLoader(l);
  145. }
  146. this.loader = l;
  147. this.nodeHash = {};
  148. /**
  149. * The root node of this tree.
  150. * @type Ext.tree.TreeNode
  151. * @property root
  152. */
  153. if(this.root){
  154. var r = this.root;
  155. delete this.root;
  156. this.setRootNode(r);
  157. }
  158. this.addEvents(
  159. /**
  160. * @event append
  161. * Fires when a new child node is appended to a node in this tree.
  162. * @param {Tree} tree The owner tree
  163. * @param {Node} parent The parent node
  164. * @param {Node} node The newly appended node
  165. * @param {Number} index The index of the newly appended node
  166. */
  167. 'append',
  168. /**
  169. * @event remove
  170. * Fires when a child node is removed from a node in this tree.
  171. * @param {Tree} tree The owner tree
  172. * @param {Node} parent The parent node
  173. * @param {Node} node The child node removed
  174. */
  175. 'remove',
  176. /**
  177. * @event movenode
  178. * Fires when a node is moved to a new location in the tree
  179. * @param {Tree} tree The owner tree
  180. * @param {Node} node The node moved
  181. * @param {Node} oldParent The old parent of this node
  182. * @param {Node} newParent The new parent of this node
  183. * @param {Number} index The index it was moved to
  184. */
  185. 'movenode',
  186. /**
  187. * @event insert
  188. * Fires when a new child node is inserted in a node in this tree.
  189. * @param {Tree} tree The owner tree
  190. * @param {Node} parent The parent node
  191. * @param {Node} node The child node inserted
  192. * @param {Node} refNode The child node the node was inserted before
  193. */
  194. 'insert',
  195. /**
  196. * @event beforeappend
  197. * Fires before a new child is appended to a node in this tree, return false to cancel the append.
  198. * @param {Tree} tree The owner tree
  199. * @param {Node} parent The parent node
  200. * @param {Node} node The child node to be appended
  201. */
  202. 'beforeappend',
  203. /**
  204. * @event beforeremove
  205. * Fires before a child is removed from a node in this tree, return false to cancel the remove.
  206. * @param {Tree} tree The owner tree
  207. * @param {Node} parent The parent node
  208. * @param {Node} node The child node to be removed
  209. */
  210. 'beforeremove',
  211. /**
  212. * @event beforemovenode
  213. * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
  214. * @param {Tree} tree The owner tree
  215. * @param {Node} node The node being moved
  216. * @param {Node} oldParent The parent of the node
  217. * @param {Node} newParent The new parent the node is moving to
  218. * @param {Number} index The index it is being moved to
  219. */
  220. 'beforemovenode',
  221. /**
  222. * @event beforeinsert
  223. * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
  224. * @param {Tree} tree The owner tree
  225. * @param {Node} parent The parent node
  226. * @param {Node} node The child node to be inserted
  227. * @param {Node} refNode The child node the node is being inserted before
  228. */
  229. 'beforeinsert',
  230. /**
  231. * @event beforeload
  232. * Fires before a node is loaded, return false to cancel
  233. * @param {Node} node The node being loaded
  234. */
  235. 'beforeload',
  236. /**
  237. * @event load
  238. * Fires when a node is loaded
  239. * @param {Node} node The node that was loaded
  240. */
  241. 'load',
  242. /**
  243. * @event textchange
  244. * Fires when the text for a node is changed
  245. * @param {Node} node The node
  246. * @param {String} text The new text
  247. * @param {String} oldText The old text
  248. */
  249. 'textchange',
  250. /**
  251. * @event beforeexpandnode
  252. * Fires before a node is expanded, return false to cancel.
  253. * @param {Node} node The node
  254. * @param {Boolean} deep
  255. * @param {Boolean} anim
  256. */
  257. 'beforeexpandnode',
  258. /**
  259. * @event beforecollapsenode
  260. * Fires before a node is collapsed, return false to cancel.
  261. * @param {Node} node The node
  262. * @param {Boolean} deep
  263. * @param {Boolean} anim
  264. */
  265. 'beforecollapsenode',
  266. /**
  267. * @event expandnode
  268. * Fires when a node is expanded
  269. * @param {Node} node The node
  270. */
  271. 'expandnode',
  272. /**
  273. * @event disabledchange
  274. * Fires when the disabled status of a node changes
  275. * @param {Node} node The node
  276. * @param {Boolean} disabled
  277. */
  278. 'disabledchange',
  279. /**
  280. * @event collapsenode
  281. * Fires when a node is collapsed
  282. * @param {Node} node The node
  283. */
  284. 'collapsenode',
  285. /**
  286. * @event beforeclick
  287. * Fires before click processing on a node. Return false to cancel the default action.
  288. * @param {Node} node The node
  289. * @param {Ext.EventObject} e The event object
  290. */
  291. 'beforeclick',
  292. /**
  293. * @event click
  294. * Fires when a node is clicked
  295. * @param {Node} node The node
  296. * @param {Ext.EventObject} e The event object
  297. */
  298. 'click',
  299. /**
  300. * @event containerclick
  301. * Fires when the tree container is clicked
  302. * @param {Tree} this
  303. * @param {Ext.EventObject} e The event object
  304. */
  305. 'containerclick',
  306. /**
  307. * @event checkchange
  308. * Fires when a node with a checkbox's checked property changes
  309. * @param {Node} this This node
  310. * @param {Boolean} checked
  311. */
  312. 'checkchange',
  313. /**
  314. * @event beforedblclick
  315. * Fires before double click processing on a node. Return false to cancel the default action.
  316. * @param {Node} node The node
  317. * @param {Ext.EventObject} e The event object
  318. */
  319. 'beforedblclick',
  320. /**
  321. * @event dblclick
  322. * Fires when a node is double clicked
  323. * @param {Node} node The node
  324. * @param {Ext.EventObject} e The event object
  325. */
  326. 'dblclick',
  327. /**
  328. * @event containerdblclick
  329. * Fires when the tree container is double clicked
  330. * @param {Tree} this
  331. * @param {Ext.EventObject} e The event object
  332. */
  333. 'containerdblclick',
  334. /**
  335. * @event contextmenu
  336. * Fires when a node is right clicked. To display a context menu in response to this
  337. * event, first create a Menu object (see {@link Ext.menu.Menu} for details), then add
  338. * a handler for this event:<pre><code>
  339. new Ext.tree.TreePanel({
  340. title: 'My TreePanel',
  341. root: new Ext.tree.AsyncTreeNode({
  342. text: 'The Root',
  343. children: [
  344. { text: 'Child node 1', leaf: true },
  345. { text: 'Child node 2', leaf: true }
  346. ]
  347. }),
  348. contextMenu: new Ext.menu.Menu({
  349. items: [{
  350. id: 'delete-node',
  351. text: 'Delete Node'
  352. }],
  353. listeners: {
  354. itemclick: function(item) {
  355. switch (item.id) {
  356. case 'delete-node':
  357. var n = item.parentMenu.contextNode;
  358. if (n.parentNode) {
  359. n.remove();
  360. }
  361. break;
  362. }
  363. }
  364. }
  365. }),
  366. listeners: {
  367. contextmenu: function(node, e) {
  368. // Register the context node with the menu so that a Menu Item's handler function can access
  369. // it via its {@link Ext.menu.BaseItem#parentMenu parentMenu} property.
  370. node.select();
  371. var c = node.getOwnerTree().contextMenu;
  372. c.contextNode = node;
  373. c.showAt(e.getXY());
  374. }
  375. }
  376. });
  377. </code></pre>
  378. * @param {Node} node The node
  379. * @param {Ext.EventObject} e The event object
  380. */
  381. 'contextmenu',
  382. /**
  383. * @event containercontextmenu
  384. * Fires when the tree container is right clicked
  385. * @param {Tree} this
  386. * @param {Ext.EventObject} e The event object
  387. */
  388. 'containercontextmenu',
  389. /**
  390. * @event beforechildrenrendered
  391. * Fires right before the child nodes for a node are rendered
  392. * @param {Node} node The node
  393. */
  394. 'beforechildrenrendered',
  395. /**
  396. * @event startdrag
  397. * Fires when a node starts being dragged
  398. * @param {Ext.tree.TreePanel} this
  399. * @param {Ext.tree.TreeNode} node
  400. * @param {event} e The raw browser event
  401. */
  402. 'startdrag',
  403. /**
  404. * @event enddrag
  405. * Fires when a drag operation is complete
  406. * @param {Ext.tree.TreePanel} this
  407. * @param {Ext.tree.TreeNode} node
  408. * @param {event} e The raw browser event
  409. */
  410. 'enddrag',
  411. /**
  412. * @event dragdrop
  413. * Fires when a dragged node is dropped on a valid DD target
  414. * @param {Ext.tree.TreePanel} this
  415. * @param {Ext.tree.TreeNode} node
  416. * @param {DD} dd The dd it was dropped on
  417. * @param {event} e The raw browser event
  418. */
  419. 'dragdrop',
  420. /**
  421. * @event beforenodedrop
  422. * Fires when a DD object is dropped on a node in this tree for preprocessing. Return false to cancel the drop. The dropEvent
  423. * passed to handlers has the following properties:<br />
  424. * <ul style="padding:5px;padding-left:16px;">
  425. * <li>tree - The TreePanel</li>
  426. * <li>target - The node being targeted for the drop</li>
  427. * <li>data - The drag data from the drag source</li>
  428. * <li>point - The point of the drop - append, above or below</li>
  429. * <li>source - The drag source</li>
  430. * <li>rawEvent - Raw mouse event</li>
  431. * <li>dropNode - Drop node(s) provided by the source <b>OR</b> you can supply node(s)
  432. * to be inserted by setting them on this object.</li>
  433. * <li>cancel - Set this to true to cancel the drop.</li>
  434. * <li>dropStatus - If the default drop action is cancelled but the drop is valid, setting this to true
  435. * will prevent the animated 'repair' from appearing.</li>
  436. * </ul>
  437. * @param {Object} dropEvent
  438. */
  439. 'beforenodedrop',
  440. /**
  441. * @event nodedrop
  442. * Fires after a DD object is dropped on a node in this tree. The dropEvent
  443. * passed to handlers has the following properties:<br />
  444. * <ul style="padding:5px;padding-left:16px;">
  445. * <li>tree - The TreePanel</li>
  446. * <li>target - The node being targeted for the drop</li>
  447. * <li>data - The drag data from the drag source</li>
  448. * <li>point - The point of the drop - append, above or below</li>
  449. * <li>source - The drag source</li>
  450. * <li>rawEvent - Raw mouse event</li>
  451. * <li>dropNode - Dropped node(s).</li>
  452. * </ul>
  453. * @param {Object} dropEvent
  454. */
  455. 'nodedrop',
  456. /**
  457. * @event nodedragover
  458. * Fires when a tree node is being targeted for a drag drop, return false to signal drop not allowed. The dragOverEvent
  459. * passed to handlers has the following properties:<br />
  460. * <ul style="padding:5px;padding-left:16px;">
  461. * <li>tree - The TreePanel</li>
  462. * <li>target - The node being targeted for the drop</li>
  463. * <li>data - The drag data from the drag source</li>
  464. * <li>point - The point of the drop - append, above or below</li>
  465. * <li>source - The drag source</li>
  466. * <li>rawEvent - Raw mouse event</li>
  467. * <li>dropNode - Drop node(s) provided by the source.</li>
  468. * <li>cancel - Set this to true to signal drop not allowed.</li>
  469. * </ul>
  470. * @param {Object} dragOverEvent
  471. */
  472. 'nodedragover'
  473. );
  474. if(this.singleExpand){
  475. this.on('beforeexpandnode', this.restrictExpand, this);
  476. }
  477. },
  478. // private
  479. proxyNodeEvent : function(ename, a1, a2, a3, a4, a5, a6){
  480. if(ename == 'collapse' || ename == 'expand' || ename == 'beforecollapse' || ename == 'beforeexpand' || ename == 'move' || ename == 'beforemove'){
  481. ename = ename+'node';
  482. }
  483. // args inline for performance while bubbling events
  484. return this.fireEvent(ename, a1, a2, a3, a4, a5, a6);
  485. },
  486. /**
  487. * Returns this root node for this tree
  488. * @return {Node}
  489. */
  490. getRootNode : function(){
  491. return this.root;
  492. },
  493. /**
  494. * Sets the root node for this tree. If the TreePanel has already rendered a root node, the
  495. * previous root node (and all of its descendants) are destroyed before the new root node is rendered.
  496. * @param {Node} node
  497. * @return {Node}
  498. */
  499. setRootNode : function(node){
  500. this.destroyRoot();
  501. if(!node.render){ // attributes passed
  502. node = this.loader.createNode(node);
  503. }
  504. this.root = node;
  505. node.ownerTree = this;
  506. node.isRoot = true;
  507. this.registerNode(node);
  508. if(!this.rootVisible){
  509. var uiP = node.attributes.uiProvider;
  510. node.ui = uiP ? new uiP(node) : new Ext.tree.RootTreeNodeUI(node);
  511. }
  512. if(this.innerCt){
  513. this.clearInnerCt();
  514. this.renderRoot();
  515. }
  516. return node;
  517. },
  518. clearInnerCt : function(){
  519. this.innerCt.update('');
  520. },
  521. // private
  522. renderRoot : function(){
  523. this.root.render();
  524. if(!this.rootVisible){
  525. this.root.renderChildren();
  526. }
  527. },
  528. /**
  529. * Gets a node in this tree by its id
  530. * @param {String} id
  531. * @return {Node}
  532. */
  533. getNodeById : function(id){
  534. return this.nodeHash[id];
  535. },
  536. // private
  537. registerNode : function(node){
  538. this.nodeHash[node.id] = node;
  539. },
  540. // private
  541. unregisterNode : function(node){
  542. delete this.nodeHash[node.id];
  543. },
  544. // private
  545. toString : function(){
  546. return '[Tree'+(this.id?' '+this.id:'')+']';
  547. },
  548. // private
  549. restrictExpand : function(node){
  550. var p = node.parentNode;
  551. if(p){
  552. if(p.expandedChild && p.expandedChild.parentNode == p){
  553. p.expandedChild.collapse();
  554. }
  555. p.expandedChild = node;
  556. }
  557. },
  558. /**
  559. * Retrieve an array of checked nodes, or an array of a specific attribute of checked nodes (e.g. 'id')
  560. * @param {String} attribute (optional) Defaults to null (return the actual nodes)
  561. * @param {TreeNode} startNode (optional) The node to start from, defaults to the root
  562. * @return {Array}
  563. */
  564. getChecked : function(a, startNode){
  565. startNode = startNode || this.root;
  566. var r = [];
  567. var f = function(){
  568. if(this.attributes.checked){
  569. r.push(!a ? this : (a == 'id' ? this.id : this.attributes[a]));
  570. }
  571. };
  572. startNode.cascade(f);
  573. return r;
  574. },
  575. /**
  576. * Returns the default {@link Ext.tree.TreeLoader} for this TreePanel.
  577. * @return {Ext.tree.TreeLoader} The TreeLoader for this TreePanel.
  578. */
  579. getLoader : function(){
  580. return this.loader;
  581. },
  582. /**
  583. * Expand all nodes
  584. */
  585. expandAll : function(){
  586. this.root.expand(true);
  587. },
  588. /**
  589. * Collapse all nodes
  590. */
  591. collapseAll : function(){
  592. this.root.collapse(true);
  593. },
  594. /**
  595. * Returns the selection model used by this TreePanel.
  596. * @return {TreeSelectionModel} The selection model used by this TreePanel
  597. */
  598. getSelectionModel : function(){
  599. if(!this.selModel){
  600. this.selModel = new Ext.tree.DefaultSelectionModel();
  601. }
  602. return this.selModel;
  603. },
  604. /**
  605. * Expands a specified path in this TreePanel. A path can be retrieved from a node with {@link Ext.data.Node#getPath}
  606. * @param {String} path
  607. * @param {String} attr (optional) The attribute used in the path (see {@link Ext.data.Node#getPath} for more info)
  608. * @param {Function} callback (optional) The callback to call when the expand is complete. The callback will be called with
  609. * (bSuccess, oLastNode) where bSuccess is if the expand was successful and oLastNode is the last node that was expanded.
  610. */
  611. expandPath : function(path, attr, callback){
  612. attr = attr || 'id';
  613. var keys = path.split(this.pathSeparator);
  614. var curNode = this.root;
  615. if(curNode.attributes[attr] != keys[1]){ // invalid root
  616. if(callback){
  617. callback(false, null);
  618. }
  619. return;
  620. }
  621. var index = 1;
  622. var f = function(){
  623. if(++index == keys.length){
  624. if(callback){
  625. callback(true, curNode);
  626. }
  627. return;
  628. }
  629. var c = curNode.findChild(attr, keys[index]);
  630. if(!c){
  631. if(callback){
  632. callback(false, curNode);
  633. }
  634. return;
  635. }
  636. curNode = c;
  637. c.expand(false, false, f);
  638. };
  639. curNode.expand(false, false, f);
  640. },
  641. /**
  642. * Selects the node in this tree at the specified path. A path can be retrieved from a node with {@link Ext.data.Node#getPath}
  643. * @param {String} path
  644. * @param {String} attr (optional) The attribute used in the path (see {@link Ext.data.Node#getPath} for more info)
  645. * @param {Function} callback (optional) The callback to call when the selection is complete. The callback will be called with
  646. * (bSuccess, oSelNode) where bSuccess is if the selection was successful and oSelNode is the selected node.
  647. */
  648. selectPath : function(path, attr, callback){
  649. attr = attr || 'id';
  650. var keys = path.split(this.pathSeparator),
  651. v = keys.pop();
  652. if(keys.length > 1){
  653. var f = function(success, node){
  654. if(success && node){
  655. var n = node.findChild(attr, v);
  656. if(n){
  657. n.select();
  658. if(callback){
  659. callback(true, n);
  660. }
  661. }else if(callback){
  662. callback(false, n);
  663. }
  664. }else{
  665. if(callback){
  666. callback(false, n);
  667. }
  668. }
  669. };
  670. this.expandPath(keys.join(this.pathSeparator), attr, f);
  671. }else{
  672. this.root.select();
  673. if(callback){
  674. callback(true, this.root);
  675. }
  676. }
  677. },
  678. /**
  679. * Returns the underlying Element for this tree
  680. * @return {Ext.Element} The Element
  681. */
  682. getTreeEl : function(){
  683. return this.body;
  684. },
  685. // private
  686. onRender : function(ct, position){
  687. Ext.tree.TreePanel.superclass.onRender.call(this, ct, position);
  688. this.el.addClass('x-tree');
  689. this.innerCt = this.body.createChild({tag:'ul',
  690. cls:'x-tree-root-ct ' +
  691. (this.useArrows ? 'x-tree-arrows' : this.lines ? 'x-tree-lines' : 'x-tree-no-lines')});
  692. },
  693. // private
  694. initEvents : function(){
  695. Ext.tree.TreePanel.superclass.initEvents.call(this);
  696. if(this.containerScroll){
  697. Ext.dd.ScrollManager.register(this.body);
  698. }
  699. if((this.enableDD || this.enableDrop) && !this.dropZone){
  700. /**
  701. * The dropZone used by this tree if drop is enabled (see {@link #enableDD} or {@link #enableDrop})
  702. * @property dropZone
  703. * @type Ext.tree.TreeDropZone
  704. */
  705. this.dropZone = new Ext.tree.TreeDropZone(this, this.dropConfig || {
  706. ddGroup: this.ddGroup || 'TreeDD', appendOnly: this.ddAppendOnly === true
  707. });
  708. }
  709. if((this.enableDD || this.enableDrag) && !this.dragZone){
  710. /**
  711. * The dragZone used by this tree if drag is enabled (see {@link #enableDD} or {@link #enableDrag})
  712. * @property dragZone
  713. * @type Ext.tree.TreeDragZone
  714. */
  715. this.dragZone = new Ext.tree.TreeDragZone(this, this.dragConfig || {
  716. ddGroup: this.ddGroup || 'TreeDD',
  717. scroll: this.ddScroll
  718. });
  719. }
  720. this.getSelectionModel().init(this);
  721. },
  722. // private
  723. afterRender : function(){
  724. Ext.tree.TreePanel.superclass.afterRender.call(this);
  725. this.renderRoot();
  726. },
  727. beforeDestroy : function(){
  728. if(this.rendered){
  729. Ext.dd.ScrollManager.unregister(this.body);
  730. Ext.destroy(this.dropZone, this.dragZone);
  731. }
  732. this.destroyRoot();
  733. Ext.destroy(this.loader);
  734. this.nodeHash = this.root = this.loader = null;
  735. Ext.tree.TreePanel.superclass.beforeDestroy.call(this);
  736. },
  737. /**
  738. * Destroy the root node. Not included by itself because we need to pass the silent parameter.
  739. * @private
  740. */
  741. destroyRoot : function(){
  742. if(this.root && this.root.destroy){
  743. this.root.destroy(true);
  744. }
  745. }
  746. /**
  747. * @cfg {String/Number} activeItem
  748. * @hide
  749. */
  750. /**
  751. * @cfg {Boolean} autoDestroy
  752. * @hide
  753. */
  754. /**
  755. * @cfg {Object/String/Function} autoLoad
  756. * @hide
  757. */
  758. /**
  759. * @cfg {Boolean} autoWidth
  760. * @hide
  761. */
  762. /**
  763. * @cfg {Boolean/Number} bufferResize
  764. * @hide
  765. */
  766. /**
  767. * @cfg {String} defaultType
  768. * @hide
  769. */
  770. /**
  771. * @cfg {Object} defaults
  772. * @hide
  773. */
  774. /**
  775. * @cfg {Boolean} hideBorders
  776. * @hide
  777. */
  778. /**
  779. * @cfg {Mixed} items
  780. * @hide
  781. */
  782. /**
  783. * @cfg {String} layout
  784. * @hide
  785. */
  786. /**
  787. * @cfg {Object} layoutConfig
  788. * @hide
  789. */
  790. /**
  791. * @cfg {Boolean} monitorResize
  792. * @hide
  793. */
  794. /**
  795. * @property items
  796. * @hide
  797. */
  798. /**
  799. * @method cascade
  800. * @hide
  801. */
  802. /**
  803. * @method doLayout
  804. * @hide
  805. */
  806. /**
  807. * @method find
  808. * @hide
  809. */
  810. /**
  811. * @method findBy
  812. * @hide
  813. */
  814. /**
  815. * @method findById
  816. * @hide
  817. */
  818. /**
  819. * @method findByType
  820. * @hide
  821. */
  822. /**
  823. * @method getComponent
  824. * @hide
  825. */
  826. /**
  827. * @method getLayout
  828. * @hide
  829. */
  830. /**
  831. * @method getUpdater
  832. * @hide
  833. */
  834. /**
  835. * @method insert
  836. * @hide
  837. */
  838. /**
  839. * @method load
  840. * @hide
  841. */
  842. /**
  843. * @method remove
  844. * @hide
  845. */
  846. /**
  847. * @event add
  848. * @hide
  849. */
  850. /**
  851. * @method removeAll
  852. * @hide
  853. */
  854. /**
  855. * @event afterLayout
  856. * @hide
  857. */
  858. /**
  859. * @event beforeadd
  860. * @hide
  861. */
  862. /**
  863. * @event beforeremove
  864. * @hide
  865. */
  866. /**
  867. * @event remove
  868. * @hide
  869. */
  870. /**
  871. * @cfg {String} allowDomMove @hide
  872. */
  873. /**
  874. * @cfg {String} autoEl @hide
  875. */
  876. /**
  877. * @cfg {String} applyTo @hide
  878. */
  879. /**
  880. * @cfg {String} contentEl @hide
  881. */
  882. /**
  883. * @cfg {Mixed} data @hide
  884. */
  885. /**
  886. * @cfg {Mixed} tpl @hide
  887. */
  888. /**
  889. * @cfg {String} tplWriteMode @hide
  890. */
  891. /**
  892. * @cfg {String} disabledClass @hide
  893. */
  894. /**
  895. * @cfg {String} elements @hide
  896. */
  897. /**
  898. * @cfg {String} html @hide
  899. */
  900. /**
  901. * @cfg {Boolean} preventBodyReset
  902. * @hide
  903. */
  904. /**
  905. * @property disabled
  906. * @hide
  907. */
  908. /**
  909. * @method applyToMarkup
  910. * @hide
  911. */
  912. /**
  913. * @method enable
  914. * @hide
  915. */
  916. /**
  917. * @method disable
  918. * @hide
  919. */
  920. /**
  921. * @method setDisabled
  922. * @hide
  923. */
  924. });
  925. Ext.tree.TreePanel.nodeTypes = {};
  926. Ext.reg('treepanel', Ext.tree.TreePanel);Ext.tree.TreeEventModel = function(tree){
  927. this.tree = tree;
  928. this.tree.on('render', this.initEvents, this);
  929. };
  930. Ext.tree.TreeEventModel.prototype = {
  931. initEvents : function(){
  932. var t = this.tree;
  933. if(t.trackMouseOver !== false){
  934. t.mon(t.innerCt, {
  935. scope: this,
  936. mouseover: this.delegateOver,
  937. mouseout: this.delegateOut
  938. });
  939. }
  940. t.mon(t.getTreeEl(), {
  941. scope: this,
  942. click: this.delegateClick,
  943. dblclick: this.delegateDblClick,
  944. contextmenu: this.delegateContextMenu
  945. });
  946. },
  947. getNode : function(e){
  948. var t;
  949. if(t = e.getTarget('.x-tree-node-el', 10)){
  950. var id = Ext.fly(t, '_treeEvents').getAttribute('tree-node-id', 'ext');
  951. if(id){
  952. return this.tree.getNodeById(id);
  953. }
  954. }
  955. return null;
  956. },
  957. getNodeTarget : function(e){
  958. var t = e.getTarget('.x-tree-node-icon', 1);
  959. if(!t){
  960. t = e.getTarget('.x-tree-node-el', 6);
  961. }
  962. return t;
  963. },
  964. delegateOut : function(e, t){
  965. if(!this.beforeEvent(e)){
  966. return;
  967. }
  968. if(e.getTarget('.x-tree-ec-icon', 1)){
  969. var n = this.getNode(e);
  970. this.onIconOut(e, n);
  971. if(n == this.lastEcOver){
  972. delete this.lastEcOver;
  973. }
  974. }
  975. if((t = this.getNodeTarget(e)) && !e.within(t, true)){
  976. this.onNodeOut(e, this.getNode(e));
  977. }
  978. },
  979. delegateOver : function(e, t){
  980. if(!this.beforeEvent(e)){
  981. return;
  982. }
  983. if(Ext.isGecko && !this.trackingDoc){ // prevent hanging in FF
  984. Ext.getBody().on('mouseover', this.trackExit, this);
  985. this.trackingDoc = true;
  986. }
  987. if(this.lastEcOver){ // prevent hung highlight
  988. this.onIconOut(e, this.lastEcOver);
  989. delete this.lastEcOver;
  990. }
  991. if(e.getTarget('.x-tree-ec-icon', 1)){
  992. this.lastEcOver = this.getNode(e);
  993. this.onIconOver(e, this.lastEcOver);
  994. }
  995. if(t = this.getNodeTarget(e)){
  996. this.onNodeOver(e, this.getNode(e));
  997. }
  998. },
  999. trackExit : function(e){
  1000. if(this.lastOverNode){
  1001. if(this.lastOverNode.ui && !e.within(this.lastOverNode.ui.getEl())){
  1002. this.onNodeOut(e, this.lastOverNode);
  1003. }
  1004. delete this.lastOverNode;
  1005. Ext.getBody().un('mouseover', this.trackExit, this);
  1006. this.trackingDoc = false;
  1007. }
  1008. },
  1009. delegateClick : function(e, t){
  1010. if(this.beforeEvent(e)){
  1011. if(e.getTarget('input[type=checkbox]', 1)){
  1012. this.onCheckboxClick(e, this.getNode(e));
  1013. }else if(e.getTarget('.x-tree-ec-icon', 1)){
  1014. this.onIconClick(e, this.getNode(e));
  1015. }else if(this.getNodeTarget(e)){
  1016. this.onNodeClick(e, this.getNode(e));
  1017. }
  1018. }else{
  1019. this.checkContainerEvent(e, 'click');
  1020. }
  1021. },
  1022. delegateDblClick : function(e, t){
  1023. if(this.beforeEvent(e)){
  1024. if(this.getNodeTarget(e)){
  1025. this.onNodeDblClick(e, this.getNode(e));
  1026. }
  1027. }else{
  1028. this.checkContainerEvent(e, 'dblclick');
  1029. }
  1030. },
  1031. delegateContextMenu : function(e, t){
  1032. if(this.beforeEvent(e)){
  1033. if(this.getNodeTarget(e)){
  1034. this.onNodeContextMenu(e, this.getNode(e));
  1035. }
  1036. }else{
  1037. this.checkContainerEvent(e, 'contextmenu');
  1038. }
  1039. },
  1040. checkContainerEvent: function(e, type){
  1041. if(this.disabled){
  1042. e.stopEvent();
  1043. return false;
  1044. }
  1045. this.onContainerEvent(e, type);
  1046. },
  1047. onContainerEvent: function(e, type){
  1048. this.tree.fireEvent('container' + type, this.tree, e);
  1049. },
  1050. onNodeClick : function(e, node){
  1051. node.ui.onClick(e);
  1052. },
  1053. onNodeOver : function(e, node){
  1054. this.lastOverNode = node;
  1055. node.ui.onOver(e);
  1056. },
  1057. onNodeOut : function(e, node){
  1058. node.ui.onOut(e);
  1059. },
  1060. onIconOver : function(e, node){
  1061. node.ui.addClass('x-tree-ec-over');
  1062. },
  1063. onIconOut : function(e, node){
  1064. node.ui.removeClass('x-tree-ec-over');
  1065. },
  1066. onIconClick : function(e, node){
  1067. node.ui.ecClick(e);
  1068. },
  1069. onCheckboxClick : function(e, node){
  1070. node.ui.onCheckChange(e);
  1071. },
  1072. onNodeDblClick : function(e, node){
  1073. node.ui.onDblClick(e);
  1074. },
  1075. onNodeContextMenu : function(e, node){
  1076. node.ui.onContextMenu(e);
  1077. },
  1078. beforeEvent : function(e){
  1079. var node = this.getNode(e);
  1080. if(this.disabled || !node || !node.ui){
  1081. e.stopEvent();
  1082. return false;
  1083. }
  1084. return true;
  1085. },
  1086. disable: function(){
  1087. this.disabled = true;
  1088. },
  1089. enable: function(){
  1090. this.disabled = false;
  1091. }
  1092. };/**
  1093. * @class Ext.tree.DefaultSelectionModel
  1094. * @extends Ext.util.Observable
  1095. * The default single selection for a TreePanel.
  1096. */
  1097. Ext.tree.DefaultSelectionModel = function(config){
  1098. this.selNode = null;
  1099. this.addEvents(
  1100. /**
  1101. * @event selectionchange
  1102. * Fires when the selected node changes
  1103. * @param {DefaultSelectionModel} this
  1104. * @param {TreeNode} node the new selection
  1105. */
  1106. 'selectionchange',
  1107. /**
  1108. * @event beforeselect
  1109. * Fires before the selected node changes, return false to cancel the change
  1110. * @param {DefaultSelectionModel} this
  1111. * @param {TreeNode} node the new selection
  1112. * @param {TreeNode} node the old selection
  1113. */
  1114. 'beforeselect'
  1115. );
  1116. Ext.apply(this, config);
  1117. Ext.tree.DefaultSelectionModel.superclass.constructor.call(this);
  1118. };
  1119. Ext.extend(Ext.tree.DefaultSelectionModel, Ext.util.Observable, {
  1120. init : function(tree){
  1121. this.tree = tree;
  1122. tree.mon(tree.getTreeEl(), 'keydown', this.onKeyDown, this);
  1123. tree.on('click', this.onNodeClick, this);
  1124. },
  1125. onNodeClick : function(node, e){
  1126. this.select(node);
  1127. },
  1128. /**
  1129. * Select a node.
  1130. * @param {TreeNode} node The node to select
  1131. * @return {TreeNode} The selected node
  1132. */
  1133. select : function(node, /* private*/ selectNextNode){
  1134. // If node is hidden, select the next node in whatever direction was being moved in.
  1135. if (!Ext.fly(node.ui.wrap).isVisible() && selectNextNode) {
  1136. return selectNextNode.call(this, node);
  1137. }
  1138. var last = this.selNode;
  1139. if(node == last){
  1140. node.ui.onSelectedChange(true);
  1141. }else if(this.fireEvent('beforeselect', this, node, last) !== false){
  1142. if(last && last.ui){
  1143. last.ui.onSelectedChange(false);
  1144. }
  1145. this.selNode = node;
  1146. node.ui.onSelectedChange(true);
  1147. this.fireEvent('selectionchange', this, node, last);
  1148. }
  1149. return node;
  1150. },
  1151. /**
  1152. * Deselect a node.
  1153. * @param {TreeNode} node The node to unselect
  1154. * @param {Boolean} silent True to stop the selectionchange event from firing.
  1155. */
  1156. unselect : function(node, silent){
  1157. if(this.selNode == node){
  1158. this.clearSelections(silent);
  1159. }
  1160. },
  1161. /**
  1162. * Clear all selections
  1163. * @param {Boolean} silent True to stop the selectionchange event from firing.
  1164. */
  1165. clearSelections : function(silent){
  1166. var n = this.selNode;
  1167. if(n){
  1168. n.ui.onSelectedChange(false);
  1169. this.selNode = null;
  1170. if(silent !== true){
  1171. this.fireEvent('selectionchange', this, null);
  1172. }
  1173. }
  1174. return n;
  1175. },
  1176. /**
  1177. * Get the selected node
  1178. * @return {TreeNode} The selected node
  1179. */
  1180. getSelectedNode : function(){
  1181. return this.selNode;
  1182. },
  1183. /**
  1184. * Returns true if the node is selected
  1185. * @param {TreeNode} node The node to check
  1186. * @return {Boolean}
  1187. */
  1188. isSelected : function(node){
  1189. return this.selNode == node;
  1190. },
  1191. /**
  1192. * Selects the node above the selected node in the tree, intelligently walking the nodes
  1193. * @return TreeNode The new selection
  1194. */
  1195. selectPrevious : function(/* private */ s){
  1196. if(!(s = s || this.selNode || this.lastSelNode)){
  1197. return null;
  1198. }
  1199. // Here we pass in the current function to select to indicate the direction we're moving
  1200. var ps = s.previousSibling;
  1201. if(ps){
  1202. if(!ps.isExpanded() || ps.childNodes.length < 1){
  1203. return this.select(ps, this.selectPrevious);
  1204. } else{
  1205. var lc = ps.lastChild;
  1206. while(lc && lc.isExpanded() && Ext.fly(lc.ui.wrap).isVisible() && lc.childNodes.length > 0){
  1207. lc = lc.lastChild;
  1208. }
  1209. return this.select(lc, this.selectPrevious);
  1210. }
  1211. } else if(s.parentNode && (this.tree.rootVisible || !s.parentNode.isRoot)){
  1212. return this.select(s.parentNode, this.selectPrevious);
  1213. }
  1214. return null;
  1215. },
  1216. /**
  1217. * Selects the node above the selected node in the tree, intelligently walking the nodes
  1218. * @return TreeNode The new selection
  1219. */
  1220. selectNext : function(/* private */ s){
  1221. if(!(s = s || this.selNode || this.lastSelNode)){
  1222. return null;
  1223. }
  1224. // Here we pass in the current function to select to indicate the direction we're moving
  1225. if(s.firstChild && s.isExpanded() && Ext.fly(s.ui.wrap).isVisible()){
  1226. return this.select(s.firstChild, this.selectNext);
  1227. }else if(s.nextSibling){
  1228. return this.select(s.nextSibling, this.selectNext);
  1229. }else if(s.parentNode){
  1230. var newS = null;
  1231. s.parentNode.bubble(function(){
  1232. if(this.nextSibling){
  1233. newS = this.getOwnerTree().selModel.select(this.nextSibling, this.selectNext);
  1234. return false;
  1235. }
  1236. });
  1237. return newS;
  1238. }
  1239. return null;
  1240. },
  1241. onKeyDown : function(e){
  1242. var s = this.selNode || this.lastSelNode;
  1243. // undesirable, but required
  1244. var sm = this;
  1245. if(!s){
  1246. return;
  1247. }
  1248. var k = e.getKey();
  1249. switch(k){
  1250. case e.DOWN:
  1251. e.stopEvent();
  1252. this.selectNext();
  1253. break;
  1254. case e.UP:
  1255. e.stopEvent();
  1256. this.selectPrevious();
  1257. break;
  1258. case e.RIGHT:
  1259. e.preventDefault();
  1260. if(s.hasChildNodes()){
  1261. if(!s.isExpanded()){
  1262. s.expand();
  1263. }else if(s.firstChild){
  1264. this.select(s.firstChild, e);
  1265. }
  1266. }
  1267. break;
  1268. case e.LEFT:
  1269. e.preventDefault();
  1270. if(s.hasChildNodes() && s.isExpanded()){
  1271. s.collapse();
  1272. }else if(s.parentNode && (this.tree.rootVisible || s.parentNode != this.tree.getRootNode())){
  1273. this.select(s.parentNode, e);
  1274. }
  1275. break;
  1276. };
  1277. }
  1278. });
  1279. /**
  1280. * @class Ext.tree.MultiSelectionModel
  1281. * @extends Ext.util.Observable
  1282. * Multi selection for a TreePanel.
  1283. */
  1284. Ext.tree.MultiSelectionModel = function(config){
  1285. this.selNodes = [];
  1286. this.selMap = {};
  1287. this.addEvents(
  1288. /**
  1289. * @event selectionchange
  1290. * Fires when the selected nodes change
  1291. * @param {MultiSelectionModel} this
  1292. * @param {Array} nodes Array of the selected nodes
  1293. */
  1294. 'selectionchange'
  1295. );
  1296. Ext.apply(this, config);
  1297. Ext.tree.MultiSelectionModel.superclass.constructor.call(this);
  1298. };
  1299. Ext.extend(Ext.tree.MultiSelectionModel, Ext.util.Observable, {
  1300. init : function(tree){
  1301. this.tree = tree;
  1302. tree.mon(tree.getTreeEl(), 'keydown', this.onKeyDown, this);
  1303. tree.on('click', this.onNodeClick, this);
  1304. },
  1305. onNodeClick : function(node, e){
  1306. if(e.ctrlKey && this.isSelected(node)){
  1307. this.unselect(node);
  1308. }else{
  1309. this.select(node, e, e.ctrlKey);
  1310. }
  1311. },
  1312. /**
  1313. * Select a node.
  1314. * @param {TreeNode} node The node to select
  1315. * @param {EventObject} e (optional) An event associated with the selection
  1316. * @param {Boolean} keepExisting True to retain existing selections
  1317. * @return {TreeNode} The selected node
  1318. */
  1319. select : function(node, e, keepExisting){
  1320. if(keepExisting !== true){
  1321. this.clearSelections(true);
  1322. }
  1323. if(this.isSelected(node)){
  1324. this.lastSelNode = node;
  1325. return node;
  1326. }
  1327. this.selNodes.push(node);
  1328. this.selMap[node.id] = node;
  1329. this.lastSelNode = node;
  1330. node.ui.onSelectedChange(true);
  1331. this.fireEvent('selectionchange', this, this.selNodes);
  1332. return node;
  1333. },
  1334. /**
  1335. * Deselect a node.
  1336. * @param {TreeNode} node The node to unselect
  1337. */
  1338. unselect : function(node){
  1339. if(this.selMap[node.id]){
  1340. node.ui.onSelectedChange(false);
  1341. var sn = this.selNodes;
  1342. var index = sn.indexOf(node);
  1343. if(index != -1){
  1344. this.selNodes.splice(index, 1);
  1345. }
  1346. delete this.selMap[node.id];
  1347. this.fireEvent('selectionchange', this, this.selNodes);
  1348. }
  1349. },
  1350. /**
  1351. * Clear all selections
  1352. */
  1353. clearSelections : function(suppressEvent){
  1354. var sn = this.selNodes;
  1355. if(sn.length > 0){
  1356. for(var i = 0, len = sn.length; i < len; i++){
  1357. sn[i].ui.onSelectedChange(false);
  1358. }
  1359. this.selNodes = [];
  1360. this.selMap = {};
  1361. if(suppressEvent !== true){
  1362. this.fireEvent('selectionchange', this, this.selNodes);
  1363. }
  1364. }
  1365. },
  1366. /**
  1367. * Returns true if the node is selected
  1368. * @param {TreeNode} node The node to check
  1369. * @return {Boolean}
  1370. */
  1371. isSelected : function(node){
  1372. return this.selMap[node.id] ? true : false;
  1373. },
  1374. /**
  1375. * Returns an array of the selected nodes
  1376. * @return {Array}
  1377. */
  1378. getSelectedNodes : function(){
  1379. return this.selNodes;
  1380. },
  1381. onKeyDown : Ext.tree.DefaultSelectionModel.prototype.onKeyDown,
  1382. selectNext : Ext.tree.DefaultSelectionModel.prototype.selectNext,
  1383. selectPrevious : Ext.tree.DefaultSelectionModel.prototype.selectPrevious
  1384. });/**
  1385. * @class Ext.data.Tree
  1386. * @extends Ext.util.Observable
  1387. * Represents a tree data structure and bubbles all the events for its nodes. The nodes
  1388. * in the tree have most standard DOM functionality.
  1389. * @constructor
  1390. * @param {Node} root (optional) The root node
  1391. */
  1392. Ext.data.Tree = function(root){
  1393. this.nodeHash = {};
  1394. /**
  1395. * The root node for this tree
  1396. * @type Node
  1397. */
  1398. this.root = null;
  1399. if(root){
  1400. this.setRootNode(root);
  1401. }
  1402. this.addEvents(
  1403. /**
  1404. * @event append
  1405. * Fires when a new child node is appended to a node in this tree.
  1406. * @param {Tree} tree The owner tree
  1407. * @param {Node} parent The parent node
  1408. * @param {Node} node The newly appended node
  1409. * @param {Number} index The index of the newly appended node
  1410. */
  1411. "append",
  1412. /**
  1413. * @event remove
  1414. * Fires when a child node is removed from a node in this tree.
  1415. * @param {Tree} tree The owner tree
  1416. * @param {Node} parent The parent node
  1417. * @param {Node} node The child node removed
  1418. */
  1419. "remove",
  1420. /**
  1421. * @event move
  1422. * Fires when a node is moved to a new location in the tree
  1423. * @param {Tree} tree The owner tree
  1424. * @param {Node} node The node moved
  1425. * @param {Node} oldParent The old parent of this node
  1426. * @param {Node} newParent The new parent of this node
  1427. * @param {Number} index The index it was moved to
  1428. */
  1429. "move",
  1430. /**
  1431. * @event insert
  1432. * Fires when a new child node is inserted in a node in this tree.
  1433. * @param {Tree} tree The owner tree
  1434. * @param {Node} parent The parent node
  1435. * @param {Node} node The child node inserted
  1436. * @param {Node} refNode The child node the node was inserted before
  1437. */
  1438. "insert",
  1439. /**
  1440. * @event beforeappend
  1441. * Fires before a new child is appended to a node in this tree, return false to cancel the append.
  1442. * @param {Tree} tree The owner tree
  1443. * @param {Node} parent The parent node
  1444. * @param {Node} node The child node to be appended
  1445. */
  1446. "beforeappend",
  1447. /**
  1448. * @event beforeremove
  1449. * Fires before a child is removed from a node in this tree, return false to cancel the remove.
  1450. * @param {Tree} tree The owner tree
  1451. * @param {Node} parent The parent node
  1452. * @param {Node} node The child node to be removed
  1453. */
  1454. "beforeremove",
  1455. /**
  1456. * @event beforemove
  1457. * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
  1458. * @param {Tree} tree The owner tree
  1459. * @param {Node} node The node being moved
  1460. * @param {Node} oldParent The parent of the node
  1461. * @param {Node} newParent The new parent the node is moving to
  1462. * @param {Number} index The index it is being moved to
  1463. */
  1464. "beforemove",
  1465. /**
  1466. * @event beforeinsert
  1467. * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
  1468. * @param {Tree} tree The owner tree
  1469. * @param {Node} parent The parent node
  1470. * @param {Node} node The child node to be inserted
  1471. * @param {Node} refNode The child node the node is being inserted before
  1472. */
  1473. "beforeinsert"
  1474. );
  1475. Ext.data.Tree.superclass.constructor.call(this);
  1476. };
  1477. Ext.extend(Ext.data.Tree, Ext.util.Observable, {
  1478. /**
  1479. * @cfg {String} pathSeparator
  1480. * The token used to separate paths in node ids (defaults to '/').
  1481. */
  1482. pathSeparator: "/",
  1483. // private
  1484. proxyNodeEvent : function(){
  1485. return this.fireEvent.apply(this, arguments);
  1486. },
  1487. /**
  1488. * Returns the root node for this tree.
  1489. * @return {Node}
  1490. */
  1491. getRootNode : function(){
  1492. return this.root;
  1493. },
  1494. /**
  1495. * Sets the root node for this tree.
  1496. * @param {Node} node
  1497. * @return {Node}
  1498. */
  1499. setRootNode : function(node){
  1500. this.root = node;
  1501. node.ownerTree = this;
  1502. node.isRoot = true;
  1503. this.registerNode(node);
  1504. return node;
  1505. },
  1506. /**
  1507. * Gets a node in this tree by its id.
  1508. * @param {String} id
  1509. * @return {Node}
  1510. */
  1511. getNodeById : function(id){
  1512. return this.nodeHash[id];
  1513. },
  1514. // private
  1515. registerNode : function(node){
  1516. this.nodeHash[node.id] = node;
  1517. },
  1518. // private
  1519. unregisterNode : function(node){
  1520. delete this.nodeHash[node.id];
  1521. },
  1522. toString : function(){
  1523. return "[Tree"+(this.id?" "+this.id:"")+"]";
  1524. }
  1525. });
  1526. /**
  1527. * @class Ext.data.Node
  1528. * @extends Ext.util.Observable
  1529. * @cfg {Boolean} leaf true if this node is a leaf and does not have children
  1530. * @cfg {String} id The id for this node. If one is not specified, one is generated.
  1531. * @constructor
  1532. * @param {Object} attributes The attributes/config for the node
  1533. */
  1534. Ext.data.Node = function(attributes){
  1535. /**
  1536. * The attributes supplied for the node. You can use this property to access any custom attributes you supplied.
  1537. * @type {Object}
  1538. */
  1539. this.attributes = attributes || {};
  1540. this.leaf = this.attributes.leaf;
  1541. /**
  1542. * The node id. @type String
  1543. */
  1544. this.id = this.attributes.id;
  1545. if(!this.id){
  1546. this.id = Ext.id(null, "xnode-");
  1547. this.attributes.id = this.id;
  1548. }
  1549. /**
  1550. * All child nodes of this node. @type Array
  1551. */
  1552. this.childNodes = [];
  1553. if(!this.childNodes.indexOf){ // indexOf is a must
  1554. this.childNodes.indexOf = function(o){
  1555. for(var i = 0, len = this.length; i < len; i++){
  1556. if(this[i] == o){
  1557. return i;
  1558. }
  1559. }
  1560. return -1;
  1561. };
  1562. }
  1563. /**
  1564. * The parent node for this node. @type Node
  1565. */
  1566. this.parentNode = null;
  1567. /**
  1568. * The first direct child node of this node, or null if this node has no child nodes. @type Node
  1569. */
  1570. this.firstChild = null;
  1571. /**
  1572. * The last direct child node of this node, or null if this node has no child nodes. @type Node
  1573. */
  1574. this.lastChild = null;
  1575. /**
  1576. * The node immediately preceding this node in the tree, or null if there is no sibling node. @type Node
  1577. */
  1578. this.previousSibling = null;
  1579. /**
  1580. * The node immediately following this node in the tree, or null if there is no sibling node. @type Node
  1581. */
  1582. this.nextSibling = null;
  1583. this.addEvents({
  1584. /**
  1585. * @event append
  1586. * Fires when a new child node is appended
  1587. * @param {Tree} tree The owner tree
  1588. * @param {Node} this This node
  1589. * @param {Node} node The newly appended node
  1590. * @param {Number} index The index of the newly appended node
  1591. */
  1592. "append" : true,
  1593. /**
  1594. * @event remove
  1595. * Fires when a child node is removed
  1596. * @param {Tree} tree The owner tree
  1597. * @param {Node} this This node
  1598. * @param {Node} node The removed node
  1599. */
  1600. "remove" : true,
  1601. /**
  1602. * @event move
  1603. * Fires when this node is moved to a new location in the tree
  1604. * @param {Tree} tree The owner tree
  1605. * @param {Node} this This node
  1606. * @param {Node} oldParent The old parent of this node
  1607. * @param {Node} newParent The new parent of this node
  1608. * @param {Number} index The index it was moved to
  1609. */
  1610. "move" : true,
  1611. /**
  1612. * @event insert
  1613. * Fires when a new child node is inserted.
  1614. * @param {Tree} tree The owner tree
  1615. * @param {Node} this This node
  1616. * @param {Node} node The child node inserted
  1617. * @param {Node} refNode The child node the node was inserted before
  1618. */
  1619. "insert" : true,
  1620. /**
  1621. * @event beforeappend
  1622. * Fires before a new child is appended, return false to cancel the append.
  1623. * @param {Tree} tree The owner tree
  1624. * @param {Node} this This node
  1625. * @param {Node} node The child node to be appended
  1626. */
  1627. "beforeappend" : true,
  1628. /**
  1629. * @event beforeremove
  1630. * Fires before a child is removed, return false to cancel the remove.
  1631. * @param {Tree} tree The owner tree
  1632. * @param {Node} this This node
  1633. * @param {Node} node The child node to be removed
  1634. */
  1635. "beforeremove" : true,
  1636. /**
  1637. * @event beforemove
  1638. * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
  1639. * @param {Tree} tree The owner tree
  1640. * @param {Node} this This node
  1641. * @param {Node} oldParent The parent of this node
  1642. * @param {Node} newParent The new parent this node is moving to
  1643. * @param {Number} index The index it is being moved to
  1644. */
  1645. "beforemove" : true,
  1646. /**
  1647. * @event beforeinsert
  1648. * Fires before a new child is inserted, return false to cancel the insert.
  1649. * @param {Tree} tree The owner tree
  1650. * @param {Node} this This node
  1651. * @param {Node} node The child node to be inserted
  1652. * @param {Node} refNode The child node the node is being inserted before
  1653. */
  1654. "beforeinsert" : true
  1655. });
  1656. this.listeners = this.attributes.listeners;
  1657. Ext.data.Node.superclass.constructor.call(this);
  1658. };
  1659. Ext.extend(Ext.data.Node, Ext.util.Observable, {
  1660. // private
  1661. fireEvent : function(evtName){
  1662. // first do standard event for this node
  1663. if(Ext.data.Node.superclass.fireEvent.apply(this, arguments) === false){
  1664. return false;
  1665. }
  1666. // then bubble it up to the tree if the event wasn't cancelled
  1667. var ot = this.getOwnerTree();
  1668. if(ot){
  1669. if(ot.proxyNodeEvent.apply(ot, arguments) === false){
  1670. return false;
  1671. }
  1672. }
  1673. return true;
  1674. },
  1675. /**
  1676. * Returns true if this node is a leaf
  1677. * @return {Boolean}
  1678. */
  1679. isLeaf : function(){
  1680. return this.leaf === true;
  1681. },
  1682. // private
  1683. setFirstChild : function(node){
  1684. this.firstChild = node;
  1685. },
  1686. //private
  1687. setLastChild : function(node){
  1688. this.lastChild = node;
  1689. },
  1690. /**
  1691. * Returns true if this node is the last child of its parent
  1692. * @return {Boolean}
  1693. */
  1694. isLast : function(){
  1695. return (!this.parentNode ? true : this.parentNode.lastChild == this);
  1696. },
  1697. /**
  1698. * Returns true if this node is the first child of its parent
  1699. * @return {Boolean}
  1700. */
  1701. isFirst : function(){
  1702. return (!this.parentNode ? true : this.parentNode.firstChild == this);
  1703. },
  1704. /**
  1705. * Returns true if this node has one or more child nodes, else false.
  1706. * @return {Boolean}
  1707. */
  1708. hasChildNodes : function(){
  1709. return !this.isLeaf() && this.childNodes.length > 0;
  1710. },
  1711. /**
  1712. * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
  1713. * node attribute is explicitly specified as true (see {@link #attributes}), otherwise returns false.
  1714. * @return {Boolean}
  1715. */
  1716. isExpandable : function(){
  1717. return this.attributes.expandable || this.hasChildNodes();
  1718. },
  1719. /**
  1720. * Insert node(s) as the last child node of this node.
  1721. * @param {Node/Array} node The node or Array of nodes to append
  1722. * @return {Node} The appended node if single append, or null if an array was passed
  1723. */
  1724. appendChild : function(node){
  1725. var multi = false;
  1726. if(Ext.isArray(node)){
  1727. multi = node;
  1728. }else if(arguments.length > 1){
  1729. multi = arguments;
  1730. }
  1731. // if passed an array or multiple args do them one by one
  1732. if(multi){
  1733. for(var i = 0, len = multi.length; i < len; i++) {
  1734. this.appendChild(multi[i]);
  1735. }
  1736. }else{
  1737. if(this.fireEvent("beforeappend", this.ownerTree, this, node) === false){
  1738. return false;
  1739. }
  1740. var index = this.childNodes.length;
  1741. var oldParent = node.parentNode;
  1742. // it's a move, make sure we move it cleanly
  1743. if(oldParent){
  1744. if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index) === false){
  1745. return false;
  1746. }
  1747. oldParent.removeChild(node);
  1748. }
  1749. index = this.childNodes.length;
  1750. if(index === 0){
  1751. this.setFirstChild(node);
  1752. }
  1753. this.childNodes.push(node);
  1754. node.parentNode = this;
  1755. var ps = this.childNodes[index-1];
  1756. if(ps){
  1757. node.previousSibling = ps;
  1758. ps.nextSibling = node;
  1759. }else{
  1760. node.previousSibling = null;
  1761. }
  1762. node.nextSibling = null;
  1763. this.setLastChild(node);
  1764. node.setOwnerTree(this.getOwnerTree());
  1765. this.fireEvent("append", this.ownerTree, this, node, index);
  1766. if(oldParent){
  1767. node.fireEvent("move", this.ownerTree, node, oldParent, this, index);
  1768. }
  1769. return node;
  1770. }
  1771. },
  1772. /**
  1773. * Removes a child node from this node.
  1774. * @param {Node} node The node to remove
  1775. * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
  1776. * @return {Node} The removed node
  1777. */
  1778. removeChild : function(node, destroy){
  1779. var index = this.childNodes.indexOf(node);
  1780. if(index == -1){
  1781. return false;
  1782. }
  1783. if(this.fireEvent("beforeremove", this.ownerTree, this, node) === false){
  1784. return false;
  1785. }
  1786. // remove it from childNodes collection
  1787. this.childNodes.splice(index, 1);
  1788. // update siblings
  1789. if(node.previousSibling){
  1790. node.previousSibling.nextSibling = node.nextSibling;
  1791. }
  1792. if(node.nextSibling){
  1793. node.nextSibling.previousSibling = node.previousSibling;
  1794. }
  1795. // update child refs
  1796. if(this.firstChild == node){
  1797. this.setFirstChild(node.nextSibling);
  1798. }
  1799. if(this.lastChild == node){
  1800. this.setLastChild(node.previousSibling);
  1801. }
  1802. this.fireEvent("remove", this.ownerTree, this, node);
  1803. if(destroy){
  1804. node.destroy(true);
  1805. }else{
  1806. node.clear();
  1807. }
  1808. return node;
  1809. },
  1810. // private
  1811. clear : function(destroy){
  1812. // clear any references from the node
  1813. this.setOwnerTree(null, destroy);
  1814. this.parentNode = this.previousSibling = this.nextSibling = null;
  1815. if(destroy){
  1816. this.firstChild = this.lastChild = null;
  1817. }
  1818. },
  1819. /**
  1820. * Destroys the node.
  1821. */
  1822. destroy : function(/* private */ silent){
  1823. /*
  1824. * Silent is to be used in a number of cases
  1825. * 1) When setRootNode is called.
  1826. * 2) When destroy on the tree is called
  1827. * 3) For destroying child nodes on a node
  1828. */
  1829. if(silent === true){
  1830. this.purgeListeners();
  1831. this.clear(true);
  1832. Ext.each(this.childNodes, function(n){
  1833. n.destroy(true);
  1834. });
  1835. this.childNodes = null;
  1836. }else{
  1837. this.remove(true);
  1838. }
  1839. },
  1840. /**
  1841. * Inserts the first node before the second node in this nodes childNodes collection.
  1842. * @param {Node} node The node to insert
  1843. * @param {Node} refNode The node to insert before (if null the node is appended)
  1844. * @return {Node} The inserted node
  1845. */
  1846. insertBefore : function(node, refNode){
  1847. if(!refNode){ // like standard Dom, refNode can be null for append
  1848. return this.appendChild(node);
  1849. }
  1850. // nothing to do
  1851. if(node == refNode){
  1852. return false;
  1853. }
  1854. if(this.fireEvent("beforeinsert", this.ownerTree, this, node, refNode) === false){
  1855. return false;
  1856. }
  1857. var index = this.childNodes.indexOf(refNode);
  1858. var oldParent = node.parentNode;
  1859. var refIndex = index;
  1860. // when moving internally, indexes will change after remove
  1861. if(oldParent == this && this.childNodes.indexOf(node) < index){
  1862. refIndex--;
  1863. }
  1864. // it's a move, make sure we move it cleanly
  1865. if(oldParent){
  1866. if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index, refNode) === false){
  1867. return false;
  1868. }
  1869. oldParent.removeChild(node);
  1870. }
  1871. if(refIndex === 0){
  1872. this.setFirstChild(node);
  1873. }
  1874. this.childNodes.splice(refIndex, 0, node);
  1875. node.parentNode = this;
  1876. var ps = this.childNodes[refIndex-1];
  1877. if(ps){
  1878. node.previousSibling = ps;
  1879. ps.nextSibling = node;
  1880. }else{
  1881. node.previousSibling = null;
  1882. }
  1883. node.nextSibling = refNode;
  1884. refNode.previousSibling = node;
  1885. node.setOwnerTree(this.getOwnerTree());
  1886. this.fireEvent("insert", this.ownerTree, this, node, refNode);
  1887. if(oldParent){
  1888. node.fireEvent("move", this.ownerTree, node, oldParent, this, refIndex, refNode);
  1889. }
  1890. return node;
  1891. },
  1892. /**
  1893. * Removes this node from its parent
  1894. * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
  1895. * @return {Node} this
  1896. */
  1897. remove : function(destroy){
  1898. if (this.parentNode) {
  1899. this.parentNode.removeChild(this, destroy);
  1900. }
  1901. return this;
  1902. },
  1903. /**
  1904. * Removes all child nodes from this node.
  1905. * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
  1906. * @return {Node} this
  1907. */
  1908. removeAll : function(destroy){
  1909. var cn = this.childNodes,
  1910. n;
  1911. while((n = cn[0])){
  1912. this.removeChild(n, destroy);
  1913. }
  1914. return this;
  1915. },
  1916. /**
  1917. * Returns the child node at the specified index.
  1918. * @param {Number} index
  1919. * @return {Node}
  1920. */
  1921. item : function(index){
  1922. return this.childNodes[index];
  1923. },
  1924. /**
  1925. * Replaces one child node in this node with another.
  1926. * @param {Node} newChild The replacement node
  1927. * @param {Node} oldChild The node to replace
  1928. * @return {Node} The replaced node
  1929. */
  1930. replaceChild : function(newChild, oldChild){
  1931. var s = oldChild ? oldChild.nextSibling : null;
  1932. this.removeChild(oldChild);
  1933. this.insertBefore(newChild, s);
  1934. return oldChild;
  1935. },
  1936. /**
  1937. * Returns the index of a child node
  1938. * @param {Node} node
  1939. * @return {Number} The index of the node or -1 if it was not found
  1940. */
  1941. indexOf : function(child){
  1942. return this.childNodes.indexOf(child);
  1943. },
  1944. /**
  1945. * Returns the tree this node is in.
  1946. * @return {Tree}
  1947. */
  1948. getOwnerTree : function(){
  1949. // if it doesn't have one, look for one
  1950. if(!this.ownerTree){
  1951. var p = this;
  1952. while(p){
  1953. if(p.ownerTree){
  1954. this.ownerTree = p.ownerTree;
  1955. break;
  1956. }
  1957. p = p.parentNode;
  1958. }
  1959. }
  1960. return this.ownerTree;
  1961. },
  1962. /**
  1963. * Returns depth of this node (the root node has a depth of 0)
  1964. * @return {Number}
  1965. */
  1966. getDepth : function(){
  1967. var depth = 0;
  1968. var p = this;
  1969. while(p.parentNode){
  1970. ++depth;
  1971. p = p.parentNode;
  1972. }
  1973. return depth;
  1974. },
  1975. // private
  1976. setOwnerTree : function(tree, destroy){
  1977. // if it is a move, we need to update everyone
  1978. if(tree != this.ownerTree){
  1979. if(this.ownerTree){
  1980. this.ownerTree.unregisterNode(this);
  1981. }
  1982. this.ownerTree = tree;
  1983. // If we're destroying, we don't need to recurse since it will be called on each child node
  1984. if(destroy !== true){
  1985. Ext.each(this.childNodes, function(n){
  1986. n.setOwnerTree(tree);
  1987. });
  1988. }
  1989. if(tree){
  1990. tree.registerNode(this);
  1991. }
  1992. }
  1993. },
  1994. /**
  1995. * Changes the id of this node.
  1996. * @param {String} id The new id for the node.
  1997. */
  1998. setId: function(id){
  1999. if(id !== this.id){
  2000. var t = this.ownerTree;
  2001. if(t){
  2002. t.unregisterNode(this);
  2003. }
  2004. this.id = this.attributes.id = id;
  2005. if(t){
  2006. t.registerNode(this);
  2007. }
  2008. this.onIdChange(id);
  2009. }
  2010. },
  2011. // private
  2012. onIdChange: Ext.emptyFn,
  2013. /**
  2014. * Returns the path for this node. The path can be used to expand or select this node programmatically.
  2015. * @param {String} attr (optional) The attr to use for the path (defaults to the node's id)
  2016. * @return {String} The path
  2017. */
  2018. getPath : function(attr){
  2019. attr = attr || "id";
  2020. var p = this.parentNode;
  2021. var b = [this.attributes[attr]];
  2022. while(p){
  2023. b.unshift(p.attributes[attr]);
  2024. p = p.parentNode;
  2025. }
  2026. var sep = this.getOwnerTree().pathSeparator;
  2027. return sep + b.join(sep);
  2028. },
  2029. /**
  2030. * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
  2031. * will be the args provided or the current node. If the function returns false at any point,
  2032. * the bubble is stopped.
  2033. * @param {Function} fn The function to call
  2034. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
  2035. * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
  2036. */
  2037. bubble : function(fn, scope, args){
  2038. var p = this;
  2039. while(p){
  2040. if(fn.apply(scope || p, args || [p]) === false){
  2041. break;
  2042. }
  2043. p = p.parentNode;
  2044. }
  2045. },
  2046. /**
  2047. * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
  2048. * will be the args provided or the current node. If the function returns false at any point,
  2049. * the cascade is stopped on that branch.
  2050. * @param {Function} fn The function to call
  2051. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
  2052. * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
  2053. */
  2054. cascade : function(fn, scope, args){
  2055. if(fn.apply(scope || this, args || [this]) !== false){
  2056. var cs = this.childNodes;
  2057. for(var i = 0, len = cs.length; i < len; i++) {
  2058. cs[i].cascade(fn, scope, args);
  2059. }
  2060. }
  2061. },
  2062. /**
  2063. * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
  2064. * will be the args provided or the current node. If the function returns false at any point,
  2065. * the iteration stops.
  2066. * @param {Function} fn The function to call
  2067. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node in the iteration.
  2068. * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
  2069. */
  2070. eachChild : function(fn, scope, args){
  2071. var cs = this.childNodes;
  2072. for(var i = 0, len = cs.length; i < len; i++) {
  2073. if(fn.apply(scope || this, args || [cs[i]]) === false){
  2074. break;
  2075. }
  2076. }
  2077. },
  2078. /**
  2079. * Finds the first child that has the attribute with the specified value.
  2080. * @param {String} attribute The attribute name
  2081. * @param {Mixed} value The value to search for
  2082. * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
  2083. * @return {Node} The found child or null if none was found
  2084. */
  2085. findChild : function(attribute, value, deep){
  2086. return this.findChildBy(function(){
  2087. return this.attributes[attribute] == value;
  2088. }, null, deep);
  2089. },
  2090. /**
  2091. * Finds the first child by a custom function. The child matches if the function passed returns <code>true</code>.
  2092. * @param {Function} fn A function which must return <code>true</code> if the passed Node is the required Node.
  2093. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Node being tested.
  2094. * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
  2095. * @return {Node} The found child or null if none was found
  2096. */
  2097. findChildBy : function(fn, scope, deep){
  2098. var cs = this.childNodes,
  2099. len = cs.length,
  2100. i = 0,
  2101. n,
  2102. res;
  2103. for(; i < len; i++){
  2104. n = cs[i];
  2105. if(fn.call(scope || n, n) === true){
  2106. return n;
  2107. }else if (deep){
  2108. res = n.findChildBy(fn, scope, deep);
  2109. if(res != null){
  2110. return res;
  2111. }
  2112. }
  2113. }
  2114. return null;
  2115. },
  2116. /**
  2117. * Sorts this nodes children using the supplied sort function.
  2118. * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
  2119. * @param {Object} scope (optional)The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
  2120. */
  2121. sort : function(fn, scope){
  2122. var cs = this.childNodes;
  2123. var len = cs.length;
  2124. if(len > 0){
  2125. var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;
  2126. cs.sort(sortFn);
  2127. for(var i = 0; i < len; i++){
  2128. var n = cs[i];
  2129. n.previousSibling = cs[i-1];
  2130. n.nextSibling = cs[i+1];
  2131. if(i === 0){
  2132. this.setFirstChild(n);
  2133. }
  2134. if(i == len-1){
  2135. this.setLastChild(n);
  2136. }
  2137. }
  2138. }
  2139. },
  2140. /**
  2141. * Returns true if this node is an ancestor (at any point) of the passed node.
  2142. * @param {Node} node
  2143. * @return {Boolean}
  2144. */
  2145. contains : function(node){
  2146. return node.isAncestor(this);
  2147. },
  2148. /**
  2149. * Returns true if the passed node is an ancestor (at any point) of this node.
  2150. * @param {Node} node
  2151. * @return {Boolean}
  2152. */
  2153. isAncestor : function(node){
  2154. var p = this.parentNode;
  2155. while(p){
  2156. if(p == node){
  2157. return true;
  2158. }
  2159. p = p.parentNode;
  2160. }
  2161. return false;
  2162. },
  2163. toString : function(){
  2164. return "[Node"+(this.id?" "+this.id:"")+"]";
  2165. }
  2166. });/**
  2167. * @class Ext.tree.TreeNode
  2168. * @extends Ext.data.Node
  2169. * @cfg {String} text The text for this node
  2170. * @cfg {Boolean} expanded true to start the node expanded
  2171. * @cfg {Boolean} allowDrag False to make this node undraggable if {@link #draggable} = true (defaults to true)
  2172. * @cfg {Boolean} allowDrop False if this node cannot have child nodes dropped on it (defaults to true)
  2173. * @cfg {Boolean} disabled true to start the node disabled
  2174. * @cfg {String} icon The path to an icon for the node. The preferred way to do this
  2175. * is to use the cls or iconCls attributes and add the icon via a CSS background image.
  2176. * @cfg {String} cls A css class to be added to the node
  2177. * @cfg {String} iconCls A css class to be added to the nodes icon element for applying css background images
  2178. * @cfg {String} href URL of the link used for the node (defaults to #)
  2179. * @cfg {String} hrefTarget target frame for the link
  2180. * @cfg {Boolean} hidden True to render hidden. (Defaults to false).
  2181. * @cfg {String} qtip An Ext QuickTip for the node
  2182. * @cfg {Boolean} expandable If set to true, the node will always show a plus/minus icon, even when empty
  2183. * @cfg {String} qtipCfg An Ext QuickTip config for the node (used instead of qtip)
  2184. * @cfg {Boolean} singleClickExpand True for single click expand on this node
  2185. * @cfg {Function} uiProvider A UI <b>class</b> to use for this node (defaults to Ext.tree.TreeNodeUI)
  2186. * @cfg {Boolean} checked True to render a checked checkbox for this node, false to render an unchecked checkbox
  2187. * (defaults to undefined with no checkbox rendered)
  2188. * @cfg {Boolean} draggable True to make this node draggable (defaults to false)
  2189. * @cfg {Boolean} isTarget False to not allow this node to act as a drop target (defaults to true)
  2190. * @cfg {Boolean} allowChildren False to not allow this node to have child nodes (defaults to true)
  2191. * @cfg {Boolean} editable False to not allow this node to be edited by an {@link Ext.tree.TreeEditor} (defaults to true)
  2192. * @constructor
  2193. * @param {Object/String} attributes The attributes/config for the node or just a string with the text for the node
  2194. */
  2195. Ext.tree.TreeNode = function(attributes){
  2196. attributes = attributes || {};
  2197. if(Ext.isString(attributes)){
  2198. attributes = {text: attributes};
  2199. }
  2200. this.childrenRendered = false;
  2201. this.rendered = false;
  2202. Ext.tree.TreeNode.superclass.constructor.call(this, attributes);
  2203. this.expanded = attributes.expanded === true;
  2204. this.isTarget = attributes.isTarget !== false;
  2205. this.draggable = attributes.draggable !== false && attributes.allowDrag !== false;
  2206. this.allowChildren = attributes.allowChildren !== false && attributes.allowDrop !== false;
  2207. /**
  2208. * Read-only. The text for this node. To change it use <code>{@link #setText}</code>.
  2209. * @type String
  2210. */
  2211. this.text = attributes.text;
  2212. /**
  2213. * True if this node is disabled.
  2214. * @type Boolean
  2215. */
  2216. this.disabled = attributes.disabled === true;
  2217. /**
  2218. * True if this node is hidden.
  2219. * @type Boolean
  2220. */
  2221. this.hidden = attributes.hidden === true;
  2222. this.addEvents(
  2223. /**
  2224. * @event textchange
  2225. * Fires when the text for this node is changed
  2226. * @param {Node} this This node
  2227. * @param {String} text The new text
  2228. * @param {String} oldText The old text
  2229. */
  2230. 'textchange',
  2231. /**
  2232. * @event beforeexpand
  2233. * Fires before this node is expanded, return false to cancel.
  2234. * @param {Node} this This node
  2235. * @param {Boolean} deep
  2236. * @param {Boolean} anim
  2237. */
  2238. 'beforeexpand',
  2239. /**
  2240. * @event beforecollapse
  2241. * Fires before this node is collapsed, return false to cancel.
  2242. * @param {Node} this This node
  2243. * @param {Boolean} deep
  2244. * @param {Boolean} anim
  2245. */
  2246. 'beforecollapse',
  2247. /**
  2248. * @event expand
  2249. * Fires when this node is expanded
  2250. * @param {Node} this This node
  2251. */
  2252. 'expand',
  2253. /**
  2254. * @event disabledchange
  2255. * Fires when the disabled status of this node changes
  2256. * @param {Node} this This node
  2257. * @param {Boolean} disabled
  2258. */
  2259. 'disabledchange',
  2260. /**
  2261. * @event collapse
  2262. * Fires when this node is collapsed
  2263. * @param {Node} this This node
  2264. */
  2265. 'collapse',
  2266. /**
  2267. * @event beforeclick
  2268. * Fires before click processing. Return false to cancel the default action.
  2269. * @param {Node} this This node
  2270. * @param {Ext.EventObject} e The event object
  2271. */
  2272. 'beforeclick',
  2273. /**
  2274. * @event click
  2275. * Fires when this node is clicked
  2276. * @param {Node} this This node
  2277. * @param {Ext.EventObject} e The event object
  2278. */
  2279. 'click',
  2280. /**
  2281. * @event checkchange
  2282. * Fires when a node with a checkbox's checked property changes
  2283. * @param {Node} this This node
  2284. * @param {Boolean} checked
  2285. */
  2286. 'checkchange',
  2287. /**
  2288. * @event beforedblclick
  2289. * Fires before double click processing. Return false to cancel the default action.
  2290. * @param {Node} this This node
  2291. * @param {Ext.EventObject} e The event object
  2292. */
  2293. 'beforedblclick',
  2294. /**
  2295. * @event dblclick
  2296. * Fires when this node is double clicked
  2297. * @param {Node} this This node
  2298. * @param {Ext.EventObject} e The event object
  2299. */
  2300. 'dblclick',
  2301. /**
  2302. * @event contextmenu
  2303. * Fires when this node is right clicked
  2304. * @param {Node} this This node
  2305. * @param {Ext.EventObject} e The event object
  2306. */
  2307. 'contextmenu',
  2308. /**
  2309. * @event beforechildrenrendered
  2310. * Fires right before the child nodes for this node are rendered
  2311. * @param {Node} this This node
  2312. */
  2313. 'beforechildrenrendered'
  2314. );
  2315. var uiClass = this.attributes.uiProvider || this.defaultUI || Ext.tree.TreeNodeUI;
  2316. /**
  2317. * Read-only. The UI for this node
  2318. * @type TreeNodeUI
  2319. */
  2320. this.ui = new uiClass(this);
  2321. };
  2322. Ext.extend(Ext.tree.TreeNode, Ext.data.Node, {
  2323. preventHScroll : true,
  2324. /**
  2325. * Returns true if this node is expanded
  2326. * @return {Boolean}
  2327. */
  2328. isExpanded : function(){
  2329. return this.expanded;
  2330. },
  2331. /**
  2332. * Returns the UI object for this node.
  2333. * @return {TreeNodeUI} The object which is providing the user interface for this tree
  2334. * node. Unless otherwise specified in the {@link #uiProvider}, this will be an instance
  2335. * of {@link Ext.tree.TreeNodeUI}
  2336. */
  2337. getUI : function(){
  2338. return this.ui;
  2339. },
  2340. getLoader : function(){
  2341. var owner;
  2342. return this.loader || ((owner = this.getOwnerTree()) && owner.loader ? owner.loader : (this.loader = new Ext.tree.TreeLoader()));
  2343. },
  2344. // private override
  2345. setFirstChild : function(node){
  2346. var of = this.firstChild;
  2347. Ext.tree.TreeNode.superclass.setFirstChild.call(this, node);
  2348. if(this.childrenRendered && of && node != of){
  2349. of.renderIndent(true, true);
  2350. }
  2351. if(this.rendered){
  2352. this.renderIndent(true, true);
  2353. }
  2354. },
  2355. // private override
  2356. setLastChild : function(node){
  2357. var ol = this.lastChild;
  2358. Ext.tree.TreeNode.superclass.setLastChild.call(this, node);
  2359. if(this.childrenRendered && ol && node != ol){
  2360. ol.renderIndent(true, true);
  2361. }
  2362. if(this.rendered){
  2363. this.renderIndent(true, true);
  2364. }
  2365. },
  2366. // these methods are overridden to provide lazy rendering support
  2367. // private override
  2368. appendChild : function(n){
  2369. if(!n.render && !Ext.isArray(n)){
  2370. n = this.getLoader().createNode(n);
  2371. }
  2372. var node = Ext.tree.TreeNode.superclass.appendChild.call(this, n);
  2373. if(node && this.childrenRendered){
  2374. node.render();
  2375. }
  2376. this.ui.updateExpandIcon();
  2377. return node;
  2378. },
  2379. // private override
  2380. removeChild : function(node, destroy){
  2381. this.ownerTree.getSelectionModel().unselect(node);
  2382. Ext.tree.TreeNode.superclass.removeChild.apply(this, arguments);
  2383. // only update the ui if we're not destroying
  2384. if(!destroy){
  2385. // if it's been rendered remove dom node
  2386. if(node.ui.rendered){
  2387. node.ui.remove();
  2388. }
  2389. if(this.childNodes.length < 1){
  2390. this.collapse(false, false);
  2391. }else{
  2392. this.ui.updateExpandIcon();
  2393. }
  2394. if(!this.firstChild && !this.isHiddenRoot()){
  2395. this.childrenRendered = false;
  2396. }
  2397. }
  2398. return node;
  2399. },
  2400. // private override
  2401. insertBefore : function(node, refNode){
  2402. if(!node.render){
  2403. node = this.getLoader().createNode(node);
  2404. }
  2405. var newNode = Ext.tree.TreeNode.superclass.insertBefore.call(this, node, refNode);
  2406. if(newNode && refNode && this.childrenRendered){
  2407. node.render();
  2408. }
  2409. this.ui.updateExpandIcon();
  2410. return newNode;
  2411. },
  2412. /**
  2413. * Sets the text for this node
  2414. * @param {String} text
  2415. */
  2416. setText : function(text){
  2417. var oldText = this.text;
  2418. this.text = this.attributes.text = text;
  2419. if(this.rendered){ // event without subscribing
  2420. this.ui.onTextChange(this, text, oldText);
  2421. }
  2422. this.fireEvent('textchange', this, text, oldText);
  2423. },
  2424. /**
  2425. * Triggers selection of this node
  2426. */
  2427. select : function(){
  2428. var t = this.getOwnerTree();
  2429. if(t){
  2430. t.getSelectionModel().select(this);
  2431. }
  2432. },
  2433. /**
  2434. * Triggers deselection of this node
  2435. * @param {Boolean} silent (optional) True to stop selection change events from firing.
  2436. */
  2437. unselect : function(silent){
  2438. var t = this.getOwnerTree();
  2439. if(t){
  2440. t.getSelectionModel().unselect(this, silent);
  2441. }
  2442. },
  2443. /**
  2444. * Returns true if this node is selected
  2445. * @return {Boolean}
  2446. */
  2447. isSelected : function(){
  2448. var t = this.getOwnerTree();
  2449. return t ? t.getSelectionModel().isSelected(this) : false;
  2450. },
  2451. /**
  2452. * Expand this node.
  2453. * @param {Boolean} deep (optional) True to expand all children as well
  2454. * @param {Boolean} anim (optional) false to cancel the default animation
  2455. * @param {Function} callback (optional) A callback to be called when
  2456. * expanding this node completes (does not wait for deep expand to complete).
  2457. * Called with 1 parameter, this node.
  2458. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to this TreeNode.
  2459. */
  2460. expand : function(deep, anim, callback, scope){
  2461. if(!this.expanded){
  2462. if(this.fireEvent('beforeexpand', this, deep, anim) === false){
  2463. return;
  2464. }
  2465. if(!this.childrenRendered){
  2466. this.renderChildren();
  2467. }
  2468. this.expanded = true;
  2469. if(!this.isHiddenRoot() && (this.getOwnerTree().animate && anim !== false) || anim){
  2470. this.ui.animExpand(function(){
  2471. this.fireEvent('expand', this);
  2472. this.runCallback(callback, scope || this, [this]);
  2473. if(deep === true){
  2474. this.expandChildNodes(true);
  2475. }
  2476. }.createDelegate(this));
  2477. return;
  2478. }else{
  2479. this.ui.expand();
  2480. this.fireEvent('expand', this);
  2481. this.runCallback(callback, scope || this, [this]);
  2482. }
  2483. }else{
  2484. this.runCallback(callback, scope || this, [this]);
  2485. }
  2486. if(deep === true){
  2487. this.expandChildNodes(true);
  2488. }
  2489. },
  2490. runCallback : function(cb, scope, args){
  2491. if(Ext.isFunction(cb)){
  2492. cb.apply(scope, args);
  2493. }
  2494. },
  2495. isHiddenRoot : function(){
  2496. return this.isRoot && !this.getOwnerTree().rootVisible;
  2497. },
  2498. /**
  2499. * Collapse this node.
  2500. * @param {Boolean} deep (optional) True to collapse all children as well
  2501. * @param {Boolean} anim (optional) false to cancel the default animation
  2502. * @param {Function} callback (optional) A callback to be called when
  2503. * expanding this node completes (does not wait for deep expand to complete).
  2504. * Called with 1 parameter, this node.
  2505. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to this TreeNode.
  2506. */
  2507. collapse : function(deep, anim, callback, scope){
  2508. if(this.expanded && !this.isHiddenRoot()){
  2509. if(this.fireEvent('beforecollapse', this, deep, anim) === false){
  2510. return;
  2511. }
  2512. this.expanded = false;
  2513. if((this.getOwnerTree().animate && anim !== false) || anim){
  2514. this.ui.animCollapse(function(){
  2515. this.fireEvent('collapse', this);
  2516. this.runCallback(callback, scope || this, [this]);
  2517. if(deep === true){
  2518. this.collapseChildNodes(true);
  2519. }
  2520. }.createDelegate(this));
  2521. return;
  2522. }else{
  2523. this.ui.collapse();
  2524. this.fireEvent('collapse', this);
  2525. this.runCallback(callback, scope || this, [this]);
  2526. }
  2527. }else if(!this.expanded){
  2528. this.runCallback(callback, scope || this, [this]);
  2529. }
  2530. if(deep === true){
  2531. var cs = this.childNodes;
  2532. for(var i = 0, len = cs.length; i < len; i++) {
  2533. cs[i].collapse(true, false);
  2534. }
  2535. }
  2536. },
  2537. // private
  2538. delayedExpand : function(delay){
  2539. if(!this.expandProcId){
  2540. this.expandProcId = this.expand.defer(delay, this);
  2541. }
  2542. },
  2543. // private
  2544. cancelExpand : function(){
  2545. if(this.expandProcId){
  2546. clearTimeout(this.expandProcId);
  2547. }
  2548. this.expandProcId = false;
  2549. },
  2550. /**
  2551. * Toggles expanded/collapsed state of the node
  2552. */
  2553. toggle : function(){
  2554. if(this.expanded){
  2555. this.collapse();
  2556. }else{
  2557. this.expand();
  2558. }
  2559. },
  2560. /**
  2561. * Ensures all parent nodes are expanded, and if necessary, scrolls
  2562. * the node into view.
  2563. * @param {Function} callback (optional) A function to call when the node has been made visible.
  2564. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to this TreeNode.
  2565. */
  2566. ensureVisible : function(callback, scope){
  2567. var tree = this.getOwnerTree();
  2568. tree.expandPath(this.parentNode ? this.parentNode.getPath() : this.getPath(), false, function(){
  2569. var node = tree.getNodeById(this.id); // Somehow if we don't do this, we lose changes that happened to node in the meantime
  2570. tree.getTreeEl().scrollChildIntoView(node.ui.anchor);
  2571. this.runCallback(callback, scope || this, [this]);
  2572. }.createDelegate(this));
  2573. },
  2574. /**
  2575. * Expand all child nodes
  2576. * @param {Boolean} deep (optional) true if the child nodes should also expand their child nodes
  2577. */
  2578. expandChildNodes : function(deep){
  2579. var cs = this.childNodes;
  2580. for(var i = 0, len = cs.length; i < len; i++) {
  2581. cs[i].expand(deep);
  2582. }
  2583. },
  2584. /**
  2585. * Collapse all child nodes
  2586. * @param {Boolean} deep (optional) true if the child nodes should also collapse their child nodes
  2587. */
  2588. collapseChildNodes : function(deep){
  2589. var cs = this.childNodes;
  2590. for(var i = 0, len = cs.length; i < len; i++) {
  2591. cs[i].collapse(deep);
  2592. }
  2593. },
  2594. /**
  2595. * Disables this node
  2596. */
  2597. disable : function(){
  2598. this.disabled = true;
  2599. this.unselect();
  2600. if(this.rendered && this.ui.onDisableChange){ // event without subscribing
  2601. this.ui.onDisableChange(this, true);
  2602. }
  2603. this.fireEvent('disabledchange', this, true);
  2604. },
  2605. /**
  2606. * Enables this node
  2607. */
  2608. enable : function(){
  2609. this.disabled = false;
  2610. if(this.rendered && this.ui.onDisableChange){ // event without subscribing
  2611. this.ui.onDisableChange(this, false);
  2612. }
  2613. this.fireEvent('disabledchange', this, false);
  2614. },
  2615. // private
  2616. renderChildren : function(suppressEvent){
  2617. if(suppressEvent !== false){
  2618. this.fireEvent('beforechildrenrendered', this);
  2619. }
  2620. var cs = this.childNodes;
  2621. for(var i = 0, len = cs.length; i < len; i++){
  2622. cs[i].render(true);
  2623. }
  2624. this.childrenRendered = true;
  2625. },
  2626. // private
  2627. sort : function(fn, scope){
  2628. Ext.tree.TreeNode.superclass.sort.apply(this, arguments);
  2629. if(this.childrenRendered){
  2630. var cs = this.childNodes;
  2631. for(var i = 0, len = cs.length; i < len; i++){
  2632. cs[i].render(true);
  2633. }
  2634. }
  2635. },
  2636. // private
  2637. render : function(bulkRender){
  2638. this.ui.render(bulkRender);
  2639. if(!this.rendered){
  2640. // make sure it is registered
  2641. this.getOwnerTree().registerNode(this);
  2642. this.rendered = true;
  2643. if(this.expanded){
  2644. this.expanded = false;
  2645. this.expand(false, false);
  2646. }
  2647. }
  2648. },
  2649. // private
  2650. renderIndent : function(deep, refresh){
  2651. if(refresh){
  2652. this.ui.childIndent = null;
  2653. }
  2654. this.ui.renderIndent();
  2655. if(deep === true && this.childrenRendered){
  2656. var cs = this.childNodes;
  2657. for(var i = 0, len = cs.length; i < len; i++){
  2658. cs[i].renderIndent(true, refresh);
  2659. }
  2660. }
  2661. },
  2662. beginUpdate : function(){
  2663. this.childrenRendered = false;
  2664. },
  2665. endUpdate : function(){
  2666. if(this.expanded && this.rendered){
  2667. this.renderChildren();
  2668. }
  2669. },
  2670. //inherit docs
  2671. destroy : function(silent){
  2672. if(silent === true){
  2673. this.unselect(true);
  2674. }
  2675. Ext.tree.TreeNode.superclass.destroy.call(this, silent);
  2676. Ext.destroy(this.ui, this.loader);
  2677. this.ui = this.loader = null;
  2678. },
  2679. // private
  2680. onIdChange : function(id){
  2681. this.ui.onIdChange(id);
  2682. }
  2683. });
  2684. Ext.tree.TreePanel.nodeTypes.node = Ext.tree.TreeNode;/**
  2685. * @class Ext.tree.AsyncTreeNode
  2686. * @extends Ext.tree.TreeNode
  2687. * @cfg {TreeLoader} loader A TreeLoader to be used by this node (defaults to the loader defined on the tree)
  2688. * @constructor
  2689. * @param {Object/String} attributes The attributes/config for the node or just a string with the text for the node
  2690. */
  2691. Ext.tree.AsyncTreeNode = function(config){
  2692. this.loaded = config && config.loaded === true;
  2693. this.loading = false;
  2694. Ext.tree.AsyncTreeNode.superclass.constructor.apply(this, arguments);
  2695. /**
  2696. * @event beforeload
  2697. * Fires before this node is loaded, return false to cancel
  2698. * @param {Node} this This node
  2699. */
  2700. this.addEvents('beforeload', 'load');
  2701. /**
  2702. * @event load
  2703. * Fires when this node is loaded
  2704. * @param {Node} this This node
  2705. */
  2706. /**
  2707. * The loader used by this node (defaults to using the tree's defined loader)
  2708. * @type TreeLoader
  2709. * @property loader
  2710. */
  2711. };
  2712. Ext.extend(Ext.tree.AsyncTreeNode, Ext.tree.TreeNode, {
  2713. expand : function(deep, anim, callback, scope){
  2714. if(this.loading){ // if an async load is already running, waiting til it's done
  2715. var timer;
  2716. var f = function(){
  2717. if(!this.loading){ // done loading
  2718. clearInterval(timer);
  2719. this.expand(deep, anim, callback, scope);
  2720. }
  2721. }.createDelegate(this);
  2722. timer = setInterval(f, 200);
  2723. return;
  2724. }
  2725. if(!this.loaded){
  2726. if(this.fireEvent("beforeload", this) === false){
  2727. return;
  2728. }
  2729. this.loading = true;
  2730. this.ui.beforeLoad(this);
  2731. var loader = this.loader || this.attributes.loader || this.getOwnerTree().getLoader();
  2732. if(loader){
  2733. loader.load(this, this.loadComplete.createDelegate(this, [deep, anim, callback, scope]), this);
  2734. return;
  2735. }
  2736. }
  2737. Ext.tree.AsyncTreeNode.superclass.expand.call(this, deep, anim, callback, scope);
  2738. },
  2739. /**
  2740. * Returns true if this node is currently loading
  2741. * @return {Boolean}
  2742. */
  2743. isLoading : function(){
  2744. return this.loading;
  2745. },
  2746. loadComplete : function(deep, anim, callback, scope){
  2747. this.loading = false;
  2748. this.loaded = true;
  2749. this.ui.afterLoad(this);
  2750. this.fireEvent("load", this);
  2751. this.expand(deep, anim, callback, scope);
  2752. },
  2753. /**
  2754. * Returns true if this node has been loaded
  2755. * @return {Boolean}
  2756. */
  2757. isLoaded : function(){
  2758. return this.loaded;
  2759. },
  2760. hasChildNodes : function(){
  2761. if(!this.isLeaf() && !this.loaded){
  2762. return true;
  2763. }else{
  2764. return Ext.tree.AsyncTreeNode.superclass.hasChildNodes.call(this);
  2765. }
  2766. },
  2767. /**
  2768. * Trigger a reload for this node
  2769. * @param {Function} callback
  2770. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to this Node.
  2771. */
  2772. reload : function(callback, scope){
  2773. this.collapse(false, false);
  2774. while(this.firstChild){
  2775. this.removeChild(this.firstChild).destroy();
  2776. }
  2777. this.childrenRendered = false;
  2778. this.loaded = false;
  2779. if(this.isHiddenRoot()){
  2780. this.expanded = false;
  2781. }
  2782. this.expand(false, false, callback, scope);
  2783. }
  2784. });
  2785. Ext.tree.TreePanel.nodeTypes.async = Ext.tree.AsyncTreeNode;/**
  2786. * @class Ext.tree.TreeNodeUI
  2787. * This class provides the default UI implementation for Ext TreeNodes.
  2788. * The TreeNode UI implementation is separate from the
  2789. * tree implementation, and allows customizing of the appearance of
  2790. * tree nodes.<br>
  2791. * <p>
  2792. * If you are customizing the Tree's user interface, you
  2793. * may need to extend this class, but you should never need to instantiate this class.<br>
  2794. * <p>
  2795. * This class provides access to the user interface components of an Ext TreeNode, through
  2796. * {@link Ext.tree.TreeNode#getUI}
  2797. */
  2798. Ext.tree.TreeNodeUI = function(node){
  2799. this.node = node;
  2800. this.rendered = false;
  2801. this.animating = false;
  2802. this.wasLeaf = true;
  2803. this.ecc = 'x-tree-ec-icon x-tree-elbow';
  2804. this.emptyIcon = Ext.BLANK_IMAGE_URL;
  2805. };
  2806. Ext.tree.TreeNodeUI.prototype = {
  2807. // private
  2808. removeChild : function(node){
  2809. if(this.rendered){
  2810. this.ctNode.removeChild(node.ui.getEl());
  2811. }
  2812. },
  2813. // private
  2814. beforeLoad : function(){
  2815. this.addClass("x-tree-node-loading");
  2816. },
  2817. // private
  2818. afterLoad : function(){
  2819. this.removeClass("x-tree-node-loading");
  2820. },
  2821. // private
  2822. onTextChange : function(node, text, oldText){
  2823. if(this.rendered){
  2824. this.textNode.innerHTML = text;
  2825. }
  2826. },
  2827. // private
  2828. onDisableChange : function(node, state){
  2829. this.disabled = state;
  2830. if (this.checkbox) {
  2831. this.checkbox.disabled = state;
  2832. }
  2833. if(state){
  2834. this.addClass("x-tree-node-disabled");
  2835. }else{
  2836. this.removeClass("x-tree-node-disabled");
  2837. }
  2838. },
  2839. // private
  2840. onSelectedChange : function(state){
  2841. if(state){
  2842. this.focus();
  2843. this.addClass("x-tree-selected");
  2844. }else{
  2845. //this.blur();
  2846. this.removeClass("x-tree-selected");
  2847. }
  2848. },
  2849. // private
  2850. onMove : function(tree, node, oldParent, newParent, index, refNode){
  2851. this.childIndent = null;
  2852. if(this.rendered){
  2853. var targetNode = newParent.ui.getContainer();
  2854. if(!targetNode){//target not rendered
  2855. this.holder = document.createElement("div");
  2856. this.holder.appendChild(this.wrap);
  2857. return;
  2858. }
  2859. var insertBefore = refNode ? refNode.ui.getEl() : null;
  2860. if(insertBefore){
  2861. targetNode.insertBefore(this.wrap, insertBefore);
  2862. }else{
  2863. targetNode.appendChild(this.wrap);
  2864. }
  2865. this.node.renderIndent(true, oldParent != newParent);
  2866. }
  2867. },
  2868. /**
  2869. * Adds one or more CSS classes to the node's UI element.
  2870. * Duplicate classes are automatically filtered out.
  2871. * @param {String/Array} className The CSS class to add, or an array of classes
  2872. */
  2873. addClass : function(cls){
  2874. if(this.elNode){
  2875. Ext.fly(this.elNode).addClass(cls);
  2876. }
  2877. },
  2878. /**
  2879. * Removes one or more CSS classes from the node's UI element.
  2880. * @param {String/Array} className The CSS class to remove, or an array of classes
  2881. */
  2882. removeClass : function(cls){
  2883. if(this.elNode){
  2884. Ext.fly(this.elNode).removeClass(cls);
  2885. }
  2886. },
  2887. // private
  2888. remove : function(){
  2889. if(this.rendered){
  2890. this.holder = document.createElement("div");
  2891. this.holder.appendChild(this.wrap);
  2892. }
  2893. },
  2894. // private
  2895. fireEvent : function(){
  2896. return this.node.fireEvent.apply(this.node, arguments);
  2897. },
  2898. // private
  2899. initEvents : function(){
  2900. this.node.on("move", this.onMove, this);
  2901. if(this.node.disabled){
  2902. this.onDisableChange(this.node, true);
  2903. }
  2904. if(this.node.hidden){
  2905. this.hide();
  2906. }
  2907. var ot = this.node.getOwnerTree();
  2908. var dd = ot.enableDD || ot.enableDrag || ot.enableDrop;
  2909. if(dd && (!this.node.isRoot || ot.rootVisible)){
  2910. Ext.dd.Registry.register(this.elNode, {
  2911. node: this.node,
  2912. handles: this.getDDHandles(),
  2913. isHandle: false
  2914. });
  2915. }
  2916. },
  2917. // private
  2918. getDDHandles : function(){
  2919. return [this.iconNode, this.textNode, this.elNode];
  2920. },
  2921. /**
  2922. * Hides this node.
  2923. */
  2924. hide : function(){
  2925. this.node.hidden = true;
  2926. if(this.wrap){
  2927. this.wrap.style.display = "none";
  2928. }
  2929. },
  2930. /**
  2931. * Shows this node.
  2932. */
  2933. show : function(){
  2934. this.node.hidden = false;
  2935. if(this.wrap){
  2936. this.wrap.style.display = "";
  2937. }
  2938. },
  2939. // private
  2940. onContextMenu : function(e){
  2941. if (this.node.hasListener("contextmenu") || this.node.getOwnerTree().hasListener("contextmenu")) {
  2942. e.preventDefault();
  2943. this.focus();
  2944. this.fireEvent("contextmenu", this.node, e);
  2945. }
  2946. },
  2947. // private
  2948. onClick : function(e){
  2949. if(this.dropping){
  2950. e.stopEvent();
  2951. return;
  2952. }
  2953. if(this.fireEvent("beforeclick", this.node, e) !== false){
  2954. var a = e.getTarget('a');
  2955. if(!this.disabled && this.node.attributes.href && a){
  2956. this.fireEvent("click", this.node, e);
  2957. return;
  2958. }else if(a && e.ctrlKey){
  2959. e.stopEvent();
  2960. }
  2961. e.preventDefault();
  2962. if(this.disabled){
  2963. return;
  2964. }
  2965. if(this.node.attributes.singleClickExpand && !this.animating && this.node.isExpandable()){
  2966. this.node.toggle();
  2967. }
  2968. this.fireEvent("click", this.node, e);
  2969. }else{
  2970. e.stopEvent();
  2971. }
  2972. },
  2973. // private
  2974. onDblClick : function(e){
  2975. e.preventDefault();
  2976. if(this.disabled){
  2977. return;
  2978. }
  2979. if(this.fireEvent("beforedblclick", this.node, e) !== false){
  2980. if(this.checkbox){
  2981. this.toggleCheck();
  2982. }
  2983. if(!this.animating && this.node.isExpandable()){
  2984. this.node.toggle();
  2985. }
  2986. this.fireEvent("dblclick", this.node, e);
  2987. }
  2988. },
  2989. onOver : function(e){
  2990. this.addClass('x-tree-node-over');
  2991. },
  2992. onOut : function(e){
  2993. this.removeClass('x-tree-node-over');
  2994. },
  2995. // private
  2996. onCheckChange : function(){
  2997. var checked = this.checkbox.checked;
  2998. // fix for IE6
  2999. this.checkbox.defaultChecked = checked;
  3000. this.node.attributes.checked = checked;
  3001. this.fireEvent('checkchange', this.node, checked);
  3002. },
  3003. // private
  3004. ecClick : function(e){
  3005. if(!this.animating && this.node.isExpandable()){
  3006. this.node.toggle();
  3007. }
  3008. },
  3009. // private
  3010. startDrop : function(){
  3011. this.dropping = true;
  3012. },
  3013. // delayed drop so the click event doesn't get fired on a drop
  3014. endDrop : function(){
  3015. setTimeout(function(){
  3016. this.dropping = false;
  3017. }.createDelegate(this), 50);
  3018. },
  3019. // private
  3020. expand : function(){
  3021. this.updateExpandIcon();
  3022. this.ctNode.style.display = "";
  3023. },
  3024. // private
  3025. focus : function(){
  3026. if(!this.node.preventHScroll){
  3027. try{this.anchor.focus();
  3028. }catch(e){}
  3029. }else{
  3030. try{
  3031. var noscroll = this.node.getOwnerTree().getTreeEl().dom;
  3032. var l = noscroll.scrollLeft;
  3033. this.anchor.focus();
  3034. noscroll.scrollLeft = l;
  3035. }catch(e){}
  3036. }
  3037. },
  3038. /**
  3039. * Sets the checked status of the tree node to the passed value, or, if no value was passed,
  3040. * toggles the checked status. If the node was rendered with no checkbox, this has no effect.
  3041. * @param {Boolean} value (optional) The new checked status.
  3042. */
  3043. toggleCheck : function(value){
  3044. var cb = this.checkbox;
  3045. if(cb){
  3046. cb.checked = (value === undefined ? !cb.checked : value);
  3047. this.onCheckChange();
  3048. }
  3049. },
  3050. // private
  3051. blur : function(){
  3052. try{
  3053. this.anchor.blur();
  3054. }catch(e){}
  3055. },
  3056. // private
  3057. animExpand : function(callback){
  3058. var ct = Ext.get(this.ctNode);
  3059. ct.stopFx();
  3060. if(!this.node.isExpandable()){
  3061. this.updateExpandIcon();
  3062. this.ctNode.style.display = "";
  3063. Ext.callback(callback);
  3064. return;
  3065. }
  3066. this.animating = true;
  3067. this.updateExpandIcon();
  3068. ct.slideIn('t', {
  3069. callback : function(){
  3070. this.animating = false;
  3071. Ext.callback(callback);
  3072. },
  3073. scope: this,
  3074. duration: this.node.ownerTree.duration || .25
  3075. });
  3076. },
  3077. // private
  3078. highlight : function(){
  3079. var tree = this.node.getOwnerTree();
  3080. Ext.fly(this.wrap).highlight(
  3081. tree.hlColor || "C3DAF9",
  3082. {endColor: tree.hlBaseColor}
  3083. );
  3084. },
  3085. // private
  3086. collapse : function(){
  3087. this.updateExpandIcon();
  3088. this.ctNode.style.display = "none";
  3089. },
  3090. // private
  3091. animCollapse : function(callback){
  3092. var ct = Ext.get(this.ctNode);
  3093. ct.enableDisplayMode('block');
  3094. ct.stopFx();
  3095. this.animating = true;
  3096. this.updateExpandIcon();
  3097. ct.slideOut('t', {
  3098. callback : function(){
  3099. this.animating = false;
  3100. Ext.callback(callback);
  3101. },
  3102. scope: this,
  3103. duration: this.node.ownerTree.duration || .25
  3104. });
  3105. },
  3106. // private
  3107. getContainer : function(){
  3108. return this.ctNode;
  3109. },
  3110. /**
  3111. * Returns the element which encapsulates this node.
  3112. * @return {HtmlElement} The DOM element. The default implementation uses a <code>&lt;li></code>.
  3113. */
  3114. getEl : function(){
  3115. return this.wrap;
  3116. },
  3117. // private
  3118. appendDDGhost : function(ghostNode){
  3119. ghostNode.appendChild(this.elNode.cloneNode(true));
  3120. },
  3121. // private
  3122. getDDRepairXY : function(){
  3123. return Ext.lib.Dom.getXY(this.iconNode);
  3124. },
  3125. // private
  3126. onRender : function(){
  3127. this.render();
  3128. },
  3129. // private
  3130. render : function(bulkRender){
  3131. var n = this.node, a = n.attributes;
  3132. var targetNode = n.parentNode ?
  3133. n.parentNode.ui.getContainer() : n.ownerTree.innerCt.dom;
  3134. if(!this.rendered){
  3135. this.rendered = true;
  3136. this.renderElements(n, a, targetNode, bulkRender);
  3137. if(a.qtip){
  3138. if(this.textNode.setAttributeNS){
  3139. this.textNode.setAttributeNS("ext", "qtip", a.qtip);
  3140. if(a.qtipTitle){
  3141. this.textNode.setAttributeNS("ext", "qtitle", a.qtipTitle);
  3142. }
  3143. }else{
  3144. this.textNode.setAttribute("ext:qtip", a.qtip);
  3145. if(a.qtipTitle){
  3146. this.textNode.setAttribute("ext:qtitle", a.qtipTitle);
  3147. }
  3148. }
  3149. }else if(a.qtipCfg){
  3150. a.qtipCfg.target = Ext.id(this.textNode);
  3151. Ext.QuickTips.register(a.qtipCfg);
  3152. }
  3153. this.initEvents();
  3154. if(!this.node.expanded){
  3155. this.updateExpandIcon(true);
  3156. }
  3157. }else{
  3158. if(bulkRender === true) {
  3159. targetNode.appendChild(this.wrap);
  3160. }
  3161. }
  3162. },
  3163. // private
  3164. renderElements : function(n, a, targetNode, bulkRender){
  3165. // add some indent caching, this helps performance when rendering a large tree
  3166. this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
  3167. var cb = Ext.isBoolean(a.checked),
  3168. nel,
  3169. href = a.href ? a.href : Ext.isGecko ? "" : "#",
  3170. buf = ['<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf x-unselectable ', a.cls,'" unselectable="on">',
  3171. '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
  3172. '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />',
  3173. '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on" />',
  3174. cb ? ('<input class="x-tree-node-cb" type="checkbox" ' + (a.checked ? 'checked="checked" />' : '/>')) : '',
  3175. '<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" ',
  3176. a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '><span unselectable="on">',n.text,"</span></a></div>",
  3177. '<ul class="x-tree-node-ct" style="display:none;"></ul>',
  3178. "</li>"].join('');
  3179. if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){
  3180. this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf);
  3181. }else{
  3182. this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf);
  3183. }
  3184. this.elNode = this.wrap.childNodes[0];
  3185. this.ctNode = this.wrap.childNodes[1];
  3186. var cs = this.elNode.childNodes;
  3187. this.indentNode = cs[0];
  3188. this.ecNode = cs[1];
  3189. this.iconNode = cs[2];
  3190. var index = 3;
  3191. if(cb){
  3192. this.checkbox = cs[3];
  3193. // fix for IE6
  3194. this.checkbox.defaultChecked = this.checkbox.checked;
  3195. index++;
  3196. }
  3197. this.anchor = cs[index];
  3198. this.textNode = cs[index].firstChild;
  3199. },
  3200. /**
  3201. * Returns the &lt;a> element that provides focus for the node's UI.
  3202. * @return {HtmlElement} The DOM anchor element.
  3203. */
  3204. getAnchor : function(){
  3205. return this.anchor;
  3206. },
  3207. /**
  3208. * Returns the text node.
  3209. * @return {HtmlNode} The DOM text node.
  3210. */
  3211. getTextEl : function(){
  3212. return this.textNode;
  3213. },
  3214. /**
  3215. * Returns the icon &lt;img> element.
  3216. * @return {HtmlElement} The DOM image element.
  3217. */
  3218. getIconEl : function(){
  3219. return this.iconNode;
  3220. },
  3221. /**
  3222. * Returns the checked status of the node. If the node was rendered with no
  3223. * checkbox, it returns false.
  3224. * @return {Boolean} The checked flag.
  3225. */
  3226. isChecked : function(){
  3227. return this.checkbox ? this.checkbox.checked : false;
  3228. },
  3229. // private
  3230. updateExpandIcon : function(){
  3231. if(this.rendered){
  3232. var n = this.node,
  3233. c1,
  3234. c2,
  3235. cls = n.isLast() ? "x-tree-elbow-end" : "x-tree-elbow",
  3236. hasChild = n.hasChildNodes();
  3237. if(hasChild || n.attributes.expandable){
  3238. if(n.expanded){
  3239. cls += "-minus";
  3240. c1 = "x-tree-node-collapsed";
  3241. c2 = "x-tree-node-expanded";
  3242. }else{
  3243. cls += "-plus";
  3244. c1 = "x-tree-node-expanded";
  3245. c2 = "x-tree-node-collapsed";
  3246. }
  3247. if(this.wasLeaf){
  3248. this.removeClass("x-tree-node-leaf");
  3249. this.wasLeaf = false;
  3250. }
  3251. if(this.c1 != c1 || this.c2 != c2){
  3252. Ext.fly(this.elNode).replaceClass(c1, c2);
  3253. this.c1 = c1; this.c2 = c2;
  3254. }
  3255. }else{
  3256. if(!this.wasLeaf){
  3257. Ext.fly(this.elNode).replaceClass("x-tree-node-expanded", "x-tree-node-collapsed");
  3258. delete this.c1;
  3259. delete this.c2;
  3260. this.wasLeaf = true;
  3261. }
  3262. }
  3263. var ecc = "x-tree-ec-icon "+cls;
  3264. if(this.ecc != ecc){
  3265. this.ecNode.className = ecc;
  3266. this.ecc = ecc;
  3267. }
  3268. }
  3269. },
  3270. // private
  3271. onIdChange: function(id){
  3272. if(this.rendered){
  3273. this.elNode.setAttribute('ext:tree-node-id', id);
  3274. }
  3275. },
  3276. // private
  3277. getChildIndent : function(){
  3278. if(!this.childIndent){
  3279. var buf = [],
  3280. p = this.node;
  3281. while(p){
  3282. if(!p.isRoot || (p.isRoot && p.ownerTree.rootVisible)){
  3283. if(!p.isLast()) {
  3284. buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-elbow-line" />');
  3285. } else {
  3286. buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-icon" />');
  3287. }
  3288. }
  3289. p = p.parentNode;
  3290. }
  3291. this.childIndent = buf.join("");
  3292. }
  3293. return this.childIndent;
  3294. },
  3295. // private
  3296. renderIndent : function(){
  3297. if(this.rendered){
  3298. var indent = "",
  3299. p = this.node.parentNode;
  3300. if(p){
  3301. indent = p.ui.getChildIndent();
  3302. }
  3303. if(this.indentMarkup != indent){ // don't rerender if not required
  3304. this.indentNode.innerHTML = indent;
  3305. this.indentMarkup = indent;
  3306. }
  3307. this.updateExpandIcon();
  3308. }
  3309. },
  3310. destroy : function(){
  3311. if(this.elNode){
  3312. Ext.dd.Registry.unregister(this.elNode.id);
  3313. }
  3314. Ext.each(['textnode', 'anchor', 'checkbox', 'indentNode', 'ecNode', 'iconNode', 'elNode', 'ctNode', 'wrap', 'holder'], function(el){
  3315. if(this[el]){
  3316. Ext.fly(this[el]).remove();
  3317. delete this[el];
  3318. }
  3319. }, this);
  3320. delete this.node;
  3321. }
  3322. };
  3323. /**
  3324. * @class Ext.tree.RootTreeNodeUI
  3325. * This class provides the default UI implementation for <b>root</b> Ext TreeNodes.
  3326. * The RootTreeNode UI implementation allows customizing the appearance of the root tree node.<br>
  3327. * <p>
  3328. * If you are customizing the Tree's user interface, you
  3329. * may need to extend this class, but you should never need to instantiate this class.<br>
  3330. */
  3331. Ext.tree.RootTreeNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
  3332. // private
  3333. render : function(){
  3334. if(!this.rendered){
  3335. var targetNode = this.node.ownerTree.innerCt.dom;
  3336. this.node.expanded = true;
  3337. targetNode.innerHTML = '<div class="x-tree-root-node"></div>';
  3338. this.wrap = this.ctNode = targetNode.firstChild;
  3339. }
  3340. },
  3341. collapse : Ext.emptyFn,
  3342. expand : Ext.emptyFn
  3343. });/**
  3344. * @class Ext.tree.TreeLoader
  3345. * @extends Ext.util.Observable
  3346. * A TreeLoader provides for lazy loading of an {@link Ext.tree.TreeNode}'s child
  3347. * nodes from a specified URL. The response must be a JavaScript Array definition
  3348. * whose elements are node definition objects. e.g.:
  3349. * <pre><code>
  3350. [{
  3351. id: 1,
  3352. text: 'A leaf Node',
  3353. leaf: true
  3354. },{
  3355. id: 2,
  3356. text: 'A folder Node',
  3357. children: [{
  3358. id: 3,
  3359. text: 'A child Node',
  3360. leaf: true
  3361. }]
  3362. }]
  3363. </code></pre>
  3364. * <br><br>
  3365. * A server request is sent, and child nodes are loaded only when a node is expanded.
  3366. * The loading node's id is passed to the server under the parameter name "node" to
  3367. * enable the server to produce the correct child nodes.
  3368. * <br><br>
  3369. * To pass extra parameters, an event handler may be attached to the "beforeload"
  3370. * event, and the parameters specified in the TreeLoader's baseParams property:
  3371. * <pre><code>
  3372. myTreeLoader.on("beforeload", function(treeLoader, node) {
  3373. this.baseParams.category = node.attributes.category;
  3374. }, this);
  3375. </code></pre>
  3376. * This would pass an HTTP parameter called "category" to the server containing
  3377. * the value of the Node's "category" attribute.
  3378. * @constructor
  3379. * Creates a new Treeloader.
  3380. * @param {Object} config A config object containing config properties.
  3381. */
  3382. Ext.tree.TreeLoader = function(config){
  3383. this.baseParams = {};
  3384. Ext.apply(this, config);
  3385. this.addEvents(
  3386. /**
  3387. * @event beforeload
  3388. * Fires before a network request is made to retrieve the Json text which specifies a node's children.
  3389. * @param {Object} This TreeLoader object.
  3390. * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
  3391. * @param {Object} callback The callback function specified in the {@link #load} call.
  3392. */
  3393. "beforeload",
  3394. /**
  3395. * @event load
  3396. * Fires when the node has been successfuly loaded.
  3397. * @param {Object} This TreeLoader object.
  3398. * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
  3399. * @param {Object} response The response object containing the data from the server.
  3400. */
  3401. "load",
  3402. /**
  3403. * @event loadexception
  3404. * Fires if the network request failed.
  3405. * @param {Object} This TreeLoader object.
  3406. * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
  3407. * @param {Object} response The response object containing the data from the server.
  3408. */
  3409. "loadexception"
  3410. );
  3411. Ext.tree.TreeLoader.superclass.constructor.call(this);
  3412. if(Ext.isString(this.paramOrder)){
  3413. this.paramOrder = this.paramOrder.split(/[\s,|]/);
  3414. }
  3415. };
  3416. Ext.extend(Ext.tree.TreeLoader, Ext.util.Observable, {
  3417. /**
  3418. * @cfg {String} dataUrl The URL from which to request a Json string which
  3419. * specifies an array of node definition objects representing the child nodes
  3420. * to be loaded.
  3421. */
  3422. /**
  3423. * @cfg {String} requestMethod The HTTP request method for loading data (defaults to the value of {@link Ext.Ajax#method}).
  3424. */
  3425. /**
  3426. * @cfg {String} url Equivalent to {@link #dataUrl}.
  3427. */
  3428. /**
  3429. * @cfg {Boolean} preloadChildren If set to true, the loader recursively loads "children" attributes when doing the first load on nodes.
  3430. */
  3431. /**
  3432. * @cfg {Object} baseParams (optional) An object containing properties which
  3433. * specify HTTP parameters to be passed to each request for child nodes.
  3434. */
  3435. /**
  3436. * @cfg {Object} baseAttrs (optional) An object containing attributes to be added to all nodes
  3437. * created by this loader. If the attributes sent by the server have an attribute in this object,
  3438. * they take priority.
  3439. */
  3440. /**
  3441. * @cfg {Object} uiProviders (optional) An object containing properties which
  3442. * specify custom {@link Ext.tree.TreeNodeUI} implementations. If the optional
  3443. * <i>uiProvider</i> attribute of a returned child node is a string rather
  3444. * than a reference to a TreeNodeUI implementation, then that string value
  3445. * is used as a property name in the uiProviders object.
  3446. */
  3447. uiProviders : {},
  3448. /**
  3449. * @cfg {Boolean} clearOnLoad (optional) Default to true. Remove previously existing
  3450. * child nodes before loading.
  3451. */
  3452. clearOnLoad : true,
  3453. /**
  3454. * @cfg {Array/String} paramOrder Defaults to <tt>undefined</tt>. Only used when using directFn.
  3455. * Specifies the params in the order in which they must be passed to the server-side Direct method
  3456. * as either (1) an Array of String values, or (2) a String of params delimited by either whitespace,
  3457. * comma, or pipe. For example,
  3458. * any of the following would be acceptable:<pre><code>
  3459. nodeParameter: 'node',
  3460. paramOrder: ['param1','param2','param3']
  3461. paramOrder: 'node param1 param2 param3'
  3462. paramOrder: 'param1,node,param2,param3'
  3463. paramOrder: 'param1|param2|param|node'
  3464. </code></pre>
  3465. */
  3466. paramOrder: undefined,
  3467. /**
  3468. * @cfg {Boolean} paramsAsHash Only used when using directFn.
  3469. * Send parameters as a collection of named arguments (defaults to <tt>false</tt>). Providing a
  3470. * <tt>{@link #paramOrder}</tt> nullifies this configuration.
  3471. */
  3472. paramsAsHash: false,
  3473. /**
  3474. * @cfg {String} nodeParameter The name of the parameter sent to the server which contains
  3475. * the identifier of the node. Defaults to <tt>'node'</tt>.
  3476. */
  3477. nodeParameter: 'node',
  3478. /**
  3479. * @cfg {Function} directFn
  3480. * Function to call when executing a request.
  3481. */
  3482. directFn : undefined,
  3483. /**
  3484. * Load an {@link Ext.tree.TreeNode} from the URL specified in the constructor.
  3485. * This is called automatically when a node is expanded, but may be used to reload
  3486. * a node (or append new children if the {@link #clearOnLoad} option is false.)
  3487. * @param {Ext.tree.TreeNode} node
  3488. * @param {Function} callback Function to call after the node has been loaded. The
  3489. * function is passed the TreeNode which was requested to be loaded.
  3490. * @param {Object} scope The scope (<code>this</code> reference) in which the callback is executed.
  3491. * defaults to the loaded TreeNode.
  3492. */
  3493. load : function(node, callback, scope){
  3494. if(this.clearOnLoad){
  3495. while(node.firstChild){
  3496. node.removeChild(node.firstChild);
  3497. }
  3498. }
  3499. if(this.doPreload(node)){ // preloaded json children
  3500. this.runCallback(callback, scope || node, [node]);
  3501. }else if(this.directFn || this.dataUrl || this.url){
  3502. this.requestData(node, callback, scope || node);
  3503. }
  3504. },
  3505. doPreload : function(node){
  3506. if(node.attributes.children){
  3507. if(node.childNodes.length < 1){ // preloaded?
  3508. var cs = node.attributes.children;
  3509. node.beginUpdate();
  3510. for(var i = 0, len = cs.length; i < len; i++){
  3511. var cn = node.appendChild(this.createNode(cs[i]));
  3512. if(this.preloadChildren){
  3513. this.doPreload(cn);
  3514. }
  3515. }
  3516. node.endUpdate();
  3517. }
  3518. return true;
  3519. }
  3520. return false;
  3521. },
  3522. getParams: function(node){
  3523. var bp = Ext.apply({}, this.baseParams),
  3524. np = this.nodeParameter,
  3525. po = this.paramOrder;
  3526. np && (bp[ np ] = node.id);
  3527. if(this.directFn){
  3528. var buf = [node.id];
  3529. if(po){
  3530. // reset 'buf' if the nodeParameter was included in paramOrder
  3531. if(np && po.indexOf(np) > -1){
  3532. buf = [];
  3533. }
  3534. for(var i = 0, len = po.length; i < len; i++){
  3535. buf.push(bp[ po[i] ]);
  3536. }
  3537. }else if(this.paramsAsHash){
  3538. buf = [bp];
  3539. }
  3540. return buf;
  3541. }else{
  3542. return bp;
  3543. }
  3544. },
  3545. requestData : function(node, callback, scope){
  3546. if(this.fireEvent("beforeload", this, node, callback) !== false){
  3547. if(this.directFn){
  3548. var args = this.getParams(node);
  3549. args.push(this.processDirectResponse.createDelegate(this, [{callback: callback, node: node, scope: scope}], true));
  3550. this.directFn.apply(window, args);
  3551. }else{
  3552. this.transId = Ext.Ajax.request({
  3553. method:this.requestMethod,
  3554. url: this.dataUrl||this.url,
  3555. success: this.handleResponse,
  3556. failure: this.handleFailure,
  3557. scope: this,
  3558. argument: {callback: callback, node: node, scope: scope},
  3559. params: this.getParams(node)
  3560. });
  3561. }
  3562. }else{
  3563. // if the load is cancelled, make sure we notify
  3564. // the node that we are done
  3565. this.runCallback(callback, scope || node, []);
  3566. }
  3567. },
  3568. processDirectResponse: function(result, response, args){
  3569. if(response.status){
  3570. this.handleResponse({
  3571. responseData: Ext.isArray(result) ? result : null,
  3572. responseText: result,
  3573. argument: args
  3574. });
  3575. }else{
  3576. this.handleFailure({
  3577. argument: args
  3578. });
  3579. }
  3580. },
  3581. // private
  3582. runCallback: function(cb, scope, args){
  3583. if(Ext.isFunction(cb)){
  3584. cb.apply(scope, args);
  3585. }
  3586. },
  3587. isLoading : function(){
  3588. return !!this.transId;
  3589. },
  3590. abort : function(){
  3591. if(this.isLoading()){
  3592. Ext.Ajax.abort(this.transId);
  3593. }
  3594. },
  3595. /**
  3596. * <p>Override this function for custom TreeNode node implementation, or to
  3597. * modify the attributes at creation time.</p>
  3598. * Example:<pre><code>
  3599. new Ext.tree.TreePanel({
  3600. ...
  3601. loader: new Ext.tree.TreeLoader({
  3602. url: 'dataUrl',
  3603. createNode: function(attr) {
  3604. // Allow consolidation consignments to have
  3605. // consignments dropped into them.
  3606. if (attr.isConsolidation) {
  3607. attr.iconCls = 'x-consol',
  3608. attr.allowDrop = true;
  3609. }
  3610. return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);
  3611. }
  3612. }),
  3613. ...
  3614. });
  3615. </code></pre>
  3616. * @param attr {Object} The attributes from which to create the new node.
  3617. */
  3618. createNode : function(attr){
  3619. // apply baseAttrs, nice idea Corey!
  3620. if(this.baseAttrs){
  3621. Ext.applyIf(attr, this.baseAttrs);
  3622. }
  3623. if(this.applyLoader !== false && !attr.loader){
  3624. attr.loader = this;
  3625. }
  3626. if(Ext.isString(attr.uiProvider)){
  3627. attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);
  3628. }
  3629. if(attr.nodeType){
  3630. return new Ext.tree.TreePanel.nodeTypes[attr.nodeType](attr);
  3631. }else{
  3632. return attr.leaf ?
  3633. new Ext.tree.TreeNode(attr) :
  3634. new Ext.tree.AsyncTreeNode(attr);
  3635. }
  3636. },
  3637. processResponse : function(response, node, callback, scope){
  3638. var json = response.responseText;
  3639. try {
  3640. var o = response.responseData || Ext.decode(json);
  3641. node.beginUpdate();
  3642. for(var i = 0, len = o.length; i < len; i++){
  3643. var n = this.createNode(o[i]);
  3644. if(n){
  3645. node.appendChild(n);
  3646. }
  3647. }
  3648. node.endUpdate();
  3649. this.runCallback(callback, scope || node, [node]);
  3650. }catch(e){
  3651. this.handleFailure(response);
  3652. }
  3653. },
  3654. handleResponse : function(response){
  3655. this.transId = false;
  3656. var a = response.argument;
  3657. this.processResponse(response, a.node, a.callback, a.scope);
  3658. this.fireEvent("load", this, a.node, response);
  3659. },
  3660. handleFailure : function(response){
  3661. this.transId = false;
  3662. var a = response.argument;
  3663. this.fireEvent("loadexception", this, a.node, response);
  3664. this.runCallback(a.callback, a.scope || a.node, [a.node]);
  3665. },
  3666. destroy : function(){
  3667. this.abort();
  3668. this.purgeListeners();
  3669. }
  3670. });/**
  3671. * @class Ext.tree.TreeFilter
  3672. * Note this class is experimental and doesn't update the indent (lines) or expand collapse icons of the nodes
  3673. * @param {TreePanel} tree
  3674. * @param {Object} config (optional)
  3675. */
  3676. Ext.tree.TreeFilter = function(tree, config){
  3677. this.tree = tree;
  3678. this.filtered = {};
  3679. Ext.apply(this, config);
  3680. };
  3681. Ext.tree.TreeFilter.prototype = {
  3682. clearBlank:false,
  3683. reverse:false,
  3684. autoClear:false,
  3685. remove:false,
  3686. /**
  3687. * Filter the data by a specific attribute.
  3688. * @param {String/RegExp} value Either string that the attribute value
  3689. * should start with or a RegExp to test against the attribute
  3690. * @param {String} attr (optional) The attribute passed in your node's attributes collection. Defaults to "text".
  3691. * @param {TreeNode} startNode (optional) The node to start the filter at.
  3692. */
  3693. filter : function(value, attr, startNode){
  3694. attr = attr || "text";
  3695. var f;
  3696. if(typeof value == "string"){
  3697. var vlen = value.length;
  3698. // auto clear empty filter
  3699. if(vlen == 0 && this.clearBlank){
  3700. this.clear();
  3701. return;
  3702. }
  3703. value = value.toLowerCase();
  3704. f = function(n){
  3705. return n.attributes[attr].substr(0, vlen).toLowerCase() == value;
  3706. };
  3707. }else if(value.exec){ // regex?
  3708. f = function(n){
  3709. return value.test(n.attributes[attr]);
  3710. };
  3711. }else{
  3712. throw 'Illegal filter type, must be string or regex';
  3713. }
  3714. this.filterBy(f, null, startNode);
  3715. },
  3716. /**
  3717. * Filter by a function. The passed function will be called with each
  3718. * node in the tree (or from the startNode). If the function returns true, the node is kept
  3719. * otherwise it is filtered. If a node is filtered, its children are also filtered.
  3720. * @param {Function} fn The filter function
  3721. * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
  3722. */
  3723. filterBy : function(fn, scope, startNode){
  3724. startNode = startNode || this.tree.root;
  3725. if(this.autoClear){
  3726. this.clear();
  3727. }
  3728. var af = this.filtered, rv = this.reverse;
  3729. var f = function(n){
  3730. if(n == startNode){
  3731. return true;
  3732. }
  3733. if(af[n.id]){
  3734. return false;
  3735. }
  3736. var m = fn.call(scope || n, n);
  3737. if(!m || rv){
  3738. af[n.id] = n;
  3739. n.ui.hide();
  3740. return false;
  3741. }
  3742. return true;
  3743. };
  3744. startNode.cascade(f);
  3745. if(this.remove){
  3746. for(var id in af){
  3747. if(typeof id != "function"){
  3748. var n = af[id];
  3749. if(n && n.parentNode){
  3750. n.parentNode.removeChild(n);
  3751. }
  3752. }
  3753. }
  3754. }
  3755. },
  3756. /**
  3757. * Clears the current filter. Note: with the "remove" option
  3758. * set a filter cannot be cleared.
  3759. */
  3760. clear : function(){
  3761. var t = this.tree;
  3762. var af = this.filtered;
  3763. for(var id in af){
  3764. if(typeof id != "function"){
  3765. var n = af[id];
  3766. if(n){
  3767. n.ui.show();
  3768. }
  3769. }
  3770. }
  3771. this.filtered = {};
  3772. }
  3773. };
  3774. /**
  3775. * @class Ext.tree.TreeSorter
  3776. * Provides sorting of nodes in a {@link Ext.tree.TreePanel}. The TreeSorter automatically monitors events on the
  3777. * associated TreePanel that might affect the tree's sort order (beforechildrenrendered, append, insert and textchange).
  3778. * Example usage:<br />
  3779. * <pre><code>
  3780. new Ext.tree.TreeSorter(myTree, {
  3781. folderSort: true,
  3782. dir: "desc",
  3783. sortType: function(node) {
  3784. // sort by a custom, typed attribute:
  3785. return parseInt(node.id, 10);
  3786. }
  3787. });
  3788. </code></pre>
  3789. * @constructor
  3790. * @param {TreePanel} tree
  3791. * @param {Object} config
  3792. */
  3793. Ext.tree.TreeSorter = function(tree, config){
  3794. /**
  3795. * @cfg {Boolean} folderSort True to sort leaf nodes under non-leaf nodes (defaults to false)
  3796. */
  3797. /**
  3798. * @cfg {String} property The named attribute on the node to sort by (defaults to "text"). Note that this
  3799. * property is only used if no {@link #sortType} function is specified, otherwise it is ignored.
  3800. */
  3801. /**
  3802. * @cfg {String} dir The direction to sort ("asc" or "desc," case-insensitive, defaults to "asc")
  3803. */
  3804. /**
  3805. * @cfg {String} leafAttr The attribute used to determine leaf nodes when {@link #folderSort} = true (defaults to "leaf")
  3806. */
  3807. /**
  3808. * @cfg {Boolean} caseSensitive true for case-sensitive sort (defaults to false)
  3809. */
  3810. /**
  3811. * @cfg {Function} sortType A custom "casting" function used to convert node values before sorting. The function
  3812. * will be called with a single parameter (the {@link Ext.tree.TreeNode} being evaluated) and is expected to return
  3813. * the node's sort value cast to the specific data type required for sorting. This could be used, for example, when
  3814. * a node's text (or other attribute) should be sorted as a date or numeric value. See the class description for
  3815. * example usage. Note that if a sortType is specified, any {@link #property} config will be ignored.
  3816. */
  3817. Ext.apply(this, config);
  3818. tree.on("beforechildrenrendered", this.doSort, this);
  3819. tree.on("append", this.updateSort, this);
  3820. tree.on("insert", this.updateSort, this);
  3821. tree.on("textchange", this.updateSortParent, this);
  3822. var dsc = this.dir && this.dir.toLowerCase() == "desc";
  3823. var p = this.property || "text";
  3824. var sortType = this.sortType;
  3825. var fs = this.folderSort;
  3826. var cs = this.caseSensitive === true;
  3827. var leafAttr = this.leafAttr || 'leaf';
  3828. this.sortFn = function(n1, n2){
  3829. if(fs){
  3830. if(n1.attributes[leafAttr] && !n2.attributes[leafAttr]){
  3831. return 1;
  3832. }
  3833. if(!n1.attributes[leafAttr] && n2.attributes[leafAttr]){
  3834. return -1;
  3835. }
  3836. }
  3837. var v1 = sortType ? sortType(n1) : (cs ? n1.attributes[p] : n1.attributes[p].toUpperCase());
  3838. var v2 = sortType ? sortType(n2) : (cs ? n2.attributes[p] : n2.attributes[p].toUpperCase());
  3839. if(v1 < v2){
  3840. return dsc ? +1 : -1;
  3841. }else if(v1 > v2){
  3842. return dsc ? -1 : +1;
  3843. }else{
  3844. return 0;
  3845. }
  3846. };
  3847. };
  3848. Ext.tree.TreeSorter.prototype = {
  3849. doSort : function(node){
  3850. node.sort(this.sortFn);
  3851. },
  3852. compareNodes : function(n1, n2){
  3853. return (n1.text.toUpperCase() > n2.text.toUpperCase() ? 1 : -1);
  3854. },
  3855. updateSort : function(tree, node){
  3856. if(node.childrenRendered){
  3857. this.doSort.defer(1, this, [node]);
  3858. }
  3859. },
  3860. updateSortParent : function(node){
  3861. var p = node.parentNode;
  3862. if(p && p.childrenRendered){
  3863. this.doSort.defer(1, this, [p]);
  3864. }
  3865. }
  3866. };/**
  3867. * @class Ext.tree.TreeDropZone
  3868. * @extends Ext.dd.DropZone
  3869. * @constructor
  3870. * @param {String/HTMLElement/Element} tree The {@link Ext.tree.TreePanel} for which to enable dropping
  3871. * @param {Object} config
  3872. */
  3873. if(Ext.dd.DropZone){
  3874. Ext.tree.TreeDropZone = function(tree, config){
  3875. /**
  3876. * @cfg {Boolean} allowParentInsert
  3877. * Allow inserting a dragged node between an expanded parent node and its first child that will become a
  3878. * sibling of the parent when dropped (defaults to false)
  3879. */
  3880. this.allowParentInsert = config.allowParentInsert || false;
  3881. /**
  3882. * @cfg {String} allowContainerDrop
  3883. * True if drops on the tree container (outside of a specific tree node) are allowed (defaults to false)
  3884. */
  3885. this.allowContainerDrop = config.allowContainerDrop || false;
  3886. /**
  3887. * @cfg {String} appendOnly
  3888. * True if the tree should only allow append drops (use for trees which are sorted, defaults to false)
  3889. */
  3890. this.appendOnly = config.appendOnly || false;
  3891. Ext.tree.TreeDropZone.superclass.constructor.call(this, tree.getTreeEl(), config);
  3892. /**
  3893. * The TreePanel for this drop zone
  3894. * @type Ext.tree.TreePanel
  3895. * @property
  3896. */
  3897. this.tree = tree;
  3898. /**
  3899. * Arbitrary data that can be associated with this tree and will be included in the event object that gets
  3900. * passed to any nodedragover event handler (defaults to {})
  3901. * @type Ext.tree.TreePanel
  3902. * @property
  3903. */
  3904. this.dragOverData = {};
  3905. // private
  3906. this.lastInsertClass = "x-tree-no-status";
  3907. };
  3908. Ext.extend(Ext.tree.TreeDropZone, Ext.dd.DropZone, {
  3909. /**
  3910. * @cfg {String} ddGroup
  3911. * A named drag drop group to which this object belongs. If a group is specified, then this object will only
  3912. * interact with other drag drop objects in the same group (defaults to 'TreeDD').
  3913. */
  3914. ddGroup : "TreeDD",
  3915. /**
  3916. * @cfg {String} expandDelay
  3917. * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
  3918. * over the target (defaults to 1000)
  3919. */
  3920. expandDelay : 1000,
  3921. // private
  3922. expandNode : function(node){
  3923. if(node.hasChildNodes() && !node.isExpanded()){
  3924. node.expand(false, null, this.triggerCacheRefresh.createDelegate(this));
  3925. }
  3926. },
  3927. // private
  3928. queueExpand : function(node){
  3929. this.expandProcId = this.expandNode.defer(this.expandDelay, this, [node]);
  3930. },
  3931. // private
  3932. cancelExpand : function(){
  3933. if(this.expandProcId){
  3934. clearTimeout(this.expandProcId);
  3935. this.expandProcId = false;
  3936. }
  3937. },
  3938. // private
  3939. isValidDropPoint : function(n, pt, dd, e, data){
  3940. if(!n || !data){ return false; }
  3941. var targetNode = n.node;
  3942. var dropNode = data.node;
  3943. // default drop rules
  3944. if(!(targetNode && targetNode.isTarget && pt)){
  3945. return false;
  3946. }
  3947. if(pt == "append" && targetNode.allowChildren === false){
  3948. return false;
  3949. }
  3950. if((pt == "above" || pt == "below") && (targetNode.parentNode && targetNode.parentNode.allowChildren === false)){
  3951. return false;
  3952. }
  3953. if(dropNode && (targetNode == dropNode || dropNode.contains(targetNode))){
  3954. return false;
  3955. }
  3956. // reuse the object
  3957. var overEvent = this.dragOverData;
  3958. overEvent.tree = this.tree;
  3959. overEvent.target = targetNode;
  3960. overEvent.data = data;
  3961. overEvent.point = pt;
  3962. overEvent.source = dd;
  3963. overEvent.rawEvent = e;
  3964. overEvent.dropNode = dropNode;
  3965. overEvent.cancel = false;
  3966. var result = this.tree.fireEvent("nodedragover", overEvent);
  3967. return overEvent.cancel === false && result !== false;
  3968. },
  3969. // private
  3970. getDropPoint : function(e, n, dd){
  3971. var tn = n.node;
  3972. if(tn.isRoot){
  3973. return tn.allowChildren !== false ? "append" : false; // always append for root
  3974. }
  3975. var dragEl = n.ddel;
  3976. var t = Ext.lib.Dom.getY(dragEl), b = t + dragEl.offsetHeight;
  3977. var y = Ext.lib.Event.getPageY(e);
  3978. var noAppend = tn.allowChildren === false || tn.isLeaf();
  3979. if(this.appendOnly || tn.parentNode.allowChildren === false){
  3980. return noAppend ? false : "append";
  3981. }
  3982. var noBelow = false;
  3983. if(!this.allowParentInsert){
  3984. noBelow = tn.hasChildNodes() && tn.isExpanded();
  3985. }
  3986. var q = (b - t) / (noAppend ? 2 : 3);
  3987. if(y >= t && y < (t + q)){
  3988. return "above";
  3989. }else if(!noBelow && (noAppend || y >= b-q && y <= b)){
  3990. return "below";
  3991. }else{
  3992. return "append";
  3993. }
  3994. },
  3995. // private
  3996. onNodeEnter : function(n, dd, e, data){
  3997. this.cancelExpand();
  3998. },
  3999. onContainerOver : function(dd, e, data) {
  4000. if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) {
  4001. return this.dropAllowed;
  4002. }
  4003. return this.dropNotAllowed;
  4004. },
  4005. // private
  4006. onNodeOver : function(n, dd, e, data){
  4007. var pt = this.getDropPoint(e, n, dd);
  4008. var node = n.node;
  4009. // auto node expand check
  4010. if(!this.expandProcId && pt == "append" && node.hasChildNodes() && !n.node.isExpanded()){
  4011. this.queueExpand(node);
  4012. }else if(pt != "append"){
  4013. this.cancelExpand();
  4014. }
  4015. // set the insert point style on the target node
  4016. var returnCls = this.dropNotAllowed;
  4017. if(this.isValidDropPoint(n, pt, dd, e, data)){
  4018. if(pt){
  4019. var el = n.ddel;
  4020. var cls;
  4021. if(pt == "above"){
  4022. returnCls = n.node.isFirst() ? "x-tree-drop-ok-above" : "x-tree-drop-ok-between";
  4023. cls = "x-tree-drag-insert-above";
  4024. }else if(pt == "below"){
  4025. returnCls = n.node.isLast() ? "x-tree-drop-ok-below" : "x-tree-drop-ok-between";
  4026. cls = "x-tree-drag-insert-below";
  4027. }else{
  4028. returnCls = "x-tree-drop-ok-append";
  4029. cls = "x-tree-drag-append";
  4030. }
  4031. if(this.lastInsertClass != cls){
  4032. Ext.fly(el).replaceClass(this.lastInsertClass, cls);
  4033. this.lastInsertClass = cls;
  4034. }
  4035. }
  4036. }
  4037. return returnCls;
  4038. },
  4039. // private
  4040. onNodeOut : function(n, dd, e, data){
  4041. this.cancelExpand();
  4042. this.removeDropIndicators(n);
  4043. },
  4044. // private
  4045. onNodeDrop : function(n, dd, e, data){
  4046. var point = this.getDropPoint(e, n, dd);
  4047. var targetNode = n.node;
  4048. targetNode.ui.startDrop();
  4049. if(!this.isValidDropPoint(n, point, dd, e, data)){
  4050. targetNode.ui.endDrop();
  4051. return false;
  4052. }
  4053. // first try to find the drop node
  4054. var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, point, e) : null);
  4055. return this.processDrop(targetNode, data, point, dd, e, dropNode);
  4056. },
  4057. onContainerDrop : function(dd, e, data){
  4058. if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) {
  4059. var targetNode = this.tree.getRootNode();
  4060. targetNode.ui.startDrop();
  4061. var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, 'append', e) : null);
  4062. return this.processDrop(targetNode, data, 'append', dd, e, dropNode);
  4063. }
  4064. return false;
  4065. },
  4066. // private
  4067. processDrop: function(target, data, point, dd, e, dropNode){
  4068. var dropEvent = {
  4069. tree : this.tree,
  4070. target: target,
  4071. data: data,
  4072. point: point,
  4073. source: dd,
  4074. rawEvent: e,
  4075. dropNode: dropNode,
  4076. cancel: !dropNode,
  4077. dropStatus: false
  4078. };
  4079. var retval = this.tree.fireEvent("beforenodedrop", dropEvent);
  4080. if(retval === false || dropEvent.cancel === true || !dropEvent.dropNode){
  4081. target.ui.endDrop();
  4082. return dropEvent.dropStatus;
  4083. }
  4084. target = dropEvent.target;
  4085. if(point == 'append' && !target.isExpanded()){
  4086. target.expand(false, null, function(){
  4087. this.completeDrop(dropEvent);
  4088. }.createDelegate(this));
  4089. }else{
  4090. this.completeDrop(dropEvent);
  4091. }
  4092. return true;
  4093. },
  4094. // private
  4095. completeDrop : function(de){
  4096. var ns = de.dropNode, p = de.point, t = de.target;
  4097. if(!Ext.isArray(ns)){
  4098. ns = [ns];
  4099. }
  4100. var n;
  4101. for(var i = 0, len = ns.length; i < len; i++){
  4102. n = ns[i];
  4103. if(p == "above"){
  4104. t.parentNode.insertBefore(n, t);
  4105. }else if(p == "below"){
  4106. t.parentNode.insertBefore(n, t.nextSibling);
  4107. }else{
  4108. t.appendChild(n);
  4109. }
  4110. }
  4111. n.ui.focus();
  4112. if(Ext.enableFx && this.tree.hlDrop){
  4113. n.ui.highlight();
  4114. }
  4115. t.ui.endDrop();
  4116. this.tree.fireEvent("nodedrop", de);
  4117. },
  4118. // private
  4119. afterNodeMoved : function(dd, data, e, targetNode, dropNode){
  4120. if(Ext.enableFx && this.tree.hlDrop){
  4121. dropNode.ui.focus();
  4122. dropNode.ui.highlight();
  4123. }
  4124. this.tree.fireEvent("nodedrop", this.tree, targetNode, data, dd, e);
  4125. },
  4126. // private
  4127. getTree : function(){
  4128. return this.tree;
  4129. },
  4130. // private
  4131. removeDropIndicators : function(n){
  4132. if(n && n.ddel){
  4133. var el = n.ddel;
  4134. Ext.fly(el).removeClass([
  4135. "x-tree-drag-insert-above",
  4136. "x-tree-drag-insert-below",
  4137. "x-tree-drag-append"]);
  4138. this.lastInsertClass = "_noclass";
  4139. }
  4140. },
  4141. // private
  4142. beforeDragDrop : function(target, e, id){
  4143. this.cancelExpand();
  4144. return true;
  4145. },
  4146. // private
  4147. afterRepair : function(data){
  4148. if(data && Ext.enableFx){
  4149. data.node.ui.highlight();
  4150. }
  4151. this.hideProxy();
  4152. }
  4153. });
  4154. }/**
  4155. * @class Ext.tree.TreeDragZone
  4156. * @extends Ext.dd.DragZone
  4157. * @constructor
  4158. * @param {String/HTMLElement/Element} tree The {@link Ext.tree.TreePanel} for which to enable dragging
  4159. * @param {Object} config
  4160. */
  4161. if(Ext.dd.DragZone){
  4162. Ext.tree.TreeDragZone = function(tree, config){
  4163. Ext.tree.TreeDragZone.superclass.constructor.call(this, tree.innerCt, config);
  4164. /**
  4165. * The TreePanel for this drag zone
  4166. * @type Ext.tree.TreePanel
  4167. * @property
  4168. */
  4169. this.tree = tree;
  4170. };
  4171. Ext.extend(Ext.tree.TreeDragZone, Ext.dd.DragZone, {
  4172. /**
  4173. * @cfg {String} ddGroup
  4174. * A named drag drop group to which this object belongs. If a group is specified, then this object will only
  4175. * interact with other drag drop objects in the same group (defaults to 'TreeDD').
  4176. */
  4177. ddGroup : "TreeDD",
  4178. // private
  4179. onBeforeDrag : function(data, e){
  4180. var n = data.node;
  4181. return n && n.draggable && !n.disabled;
  4182. },
  4183. // private
  4184. onInitDrag : function(e){
  4185. var data = this.dragData;
  4186. this.tree.getSelectionModel().select(data.node);
  4187. this.tree.eventModel.disable();
  4188. this.proxy.update("");
  4189. data.node.ui.appendDDGhost(this.proxy.ghost.dom);
  4190. this.tree.fireEvent("startdrag", this.tree, data.node, e);
  4191. },
  4192. // private
  4193. getRepairXY : function(e, data){
  4194. return data.node.ui.getDDRepairXY();
  4195. },
  4196. // private
  4197. onEndDrag : function(data, e){
  4198. this.tree.eventModel.enable.defer(100, this.tree.eventModel);
  4199. this.tree.fireEvent("enddrag", this.tree, data.node, e);
  4200. },
  4201. // private
  4202. onValidDrop : function(dd, e, id){
  4203. this.tree.fireEvent("dragdrop", this.tree, this.dragData.node, dd, e);
  4204. this.hideProxy();
  4205. },
  4206. // private
  4207. beforeInvalidDrop : function(e, id){
  4208. // this scrolls the original position back into view
  4209. var sm = this.tree.getSelectionModel();
  4210. sm.clearSelections();
  4211. sm.select(this.dragData.node);
  4212. },
  4213. // private
  4214. afterRepair : function(){
  4215. if (Ext.enableFx && this.tree.hlDrop) {
  4216. Ext.Element.fly(this.dragData.ddel).highlight(this.hlColor || "c3daf9");
  4217. }
  4218. this.dragging = false;
  4219. }
  4220. });
  4221. }/**
  4222. * @class Ext.tree.TreeEditor
  4223. * @extends Ext.Editor
  4224. * Provides editor functionality for inline tree node editing. Any valid {@link Ext.form.Field} subclass can be used
  4225. * as the editor field.
  4226. * @constructor
  4227. * @param {TreePanel} tree
  4228. * @param {Object} fieldConfig (optional) Either a prebuilt {@link Ext.form.Field} instance or a Field config object
  4229. * that will be applied to the default field instance (defaults to a {@link Ext.form.TextField}).
  4230. * @param {Object} config (optional) A TreeEditor config object
  4231. */
  4232. Ext.tree.TreeEditor = function(tree, fc, config){
  4233. fc = fc || {};
  4234. var field = fc.events ? fc : new Ext.form.TextField(fc);
  4235. Ext.tree.TreeEditor.superclass.constructor.call(this, field, config);
  4236. this.tree = tree;
  4237. if(!tree.rendered){
  4238. tree.on('render', this.initEditor, this);
  4239. }else{
  4240. this.initEditor(tree);
  4241. }
  4242. };
  4243. Ext.extend(Ext.tree.TreeEditor, Ext.Editor, {
  4244. /**
  4245. * @cfg {String} alignment
  4246. * The position to align to (see {@link Ext.Element#alignTo} for more details, defaults to "l-l").
  4247. */
  4248. alignment: "l-l",
  4249. // inherit
  4250. autoSize: false,
  4251. /**
  4252. * @cfg {Boolean} hideEl
  4253. * True to hide the bound element while the editor is displayed (defaults to false)
  4254. */
  4255. hideEl : false,
  4256. /**
  4257. * @cfg {String} cls
  4258. * CSS class to apply to the editor (defaults to "x-small-editor x-tree-editor")
  4259. */
  4260. cls: "x-small-editor x-tree-editor",
  4261. /**
  4262. * @cfg {Boolean} shim
  4263. * True to shim the editor if selects/iframes could be displayed beneath it (defaults to false)
  4264. */
  4265. shim:false,
  4266. // inherit
  4267. shadow:"frame",
  4268. /**
  4269. * @cfg {Number} maxWidth
  4270. * The maximum width in pixels of the editor field (defaults to 250). Note that if the maxWidth would exceed
  4271. * the containing tree element's size, it will be automatically limited for you to the container width, taking
  4272. * scroll and client offsets into account prior to each edit.
  4273. */
  4274. maxWidth: 250,
  4275. /**
  4276. * @cfg {Number} editDelay The number of milliseconds between clicks to register a double-click that will trigger
  4277. * editing on the current node (defaults to 350). If two clicks occur on the same node within this time span,
  4278. * the editor for the node will display, otherwise it will be processed as a regular click.
  4279. */
  4280. editDelay : 350,
  4281. initEditor : function(tree){
  4282. tree.on({
  4283. scope : this,
  4284. beforeclick: this.beforeNodeClick,
  4285. dblclick : this.onNodeDblClick
  4286. });
  4287. this.on({
  4288. scope : this,
  4289. complete : this.updateNode,
  4290. beforestartedit: this.fitToTree,
  4291. specialkey : this.onSpecialKey
  4292. });
  4293. this.on('startedit', this.bindScroll, this, {delay:10});
  4294. },
  4295. // private
  4296. fitToTree : function(ed, el){
  4297. var td = this.tree.getTreeEl().dom, nd = el.dom;
  4298. if(td.scrollLeft > nd.offsetLeft){ // ensure the node left point is visible
  4299. td.scrollLeft = nd.offsetLeft;
  4300. }
  4301. var w = Math.min(
  4302. this.maxWidth,
  4303. (td.clientWidth > 20 ? td.clientWidth : td.offsetWidth) - Math.max(0, nd.offsetLeft-td.scrollLeft) - /*cushion*/5);
  4304. this.setSize(w, '');
  4305. },
  4306. /**
  4307. * Edit the text of the passed {@link Ext.tree.TreeNode TreeNode}.
  4308. * @param node {Ext.tree.TreeNode} The TreeNode to edit. The TreeNode must be {@link Ext.tree.TreeNode#editable editable}.
  4309. */
  4310. triggerEdit : function(node, defer){
  4311. this.completeEdit();
  4312. if(node.attributes.editable !== false){
  4313. /**
  4314. * The {@link Ext.tree.TreeNode TreeNode} this editor is bound to. Read-only.
  4315. * @type Ext.tree.TreeNode
  4316. * @property editNode
  4317. */
  4318. this.editNode = node;
  4319. if(this.tree.autoScroll){
  4320. Ext.fly(node.ui.getEl()).scrollIntoView(this.tree.body);
  4321. }
  4322. var value = node.text || '';
  4323. if (!Ext.isGecko && Ext.isEmpty(node.text)){
  4324. node.setText('&#160;');
  4325. }
  4326. this.autoEditTimer = this.startEdit.defer(this.editDelay, this, [node.ui.textNode, value]);
  4327. return false;
  4328. }
  4329. },
  4330. // private
  4331. bindScroll : function(){
  4332. this.tree.getTreeEl().on('scroll', this.cancelEdit, this);
  4333. },
  4334. // private
  4335. beforeNodeClick : function(node, e){
  4336. clearTimeout(this.autoEditTimer);
  4337. if(this.tree.getSelectionModel().isSelected(node)){
  4338. e.stopEvent();
  4339. return this.triggerEdit(node);
  4340. }
  4341. },
  4342. onNodeDblClick : function(node, e){
  4343. clearTimeout(this.autoEditTimer);
  4344. },
  4345. // private
  4346. updateNode : function(ed, value){
  4347. this.tree.getTreeEl().un('scroll', this.cancelEdit, this);
  4348. this.editNode.setText(value);
  4349. },
  4350. // private
  4351. onHide : function(){
  4352. Ext.tree.TreeEditor.superclass.onHide.call(this);
  4353. if(this.editNode){
  4354. this.editNode.ui.focus.defer(50, this.editNode.ui);
  4355. }
  4356. },
  4357. // private
  4358. onSpecialKey : function(field, e){
  4359. var k = e.getKey();
  4360. if(k == e.ESC){
  4361. e.stopEvent();
  4362. this.cancelEdit();
  4363. }else if(k == e.ENTER && !e.hasModifier()){
  4364. e.stopEvent();
  4365. this.completeEdit();
  4366. }
  4367. },
  4368. onDestroy : function(){
  4369. clearTimeout(this.autoEditTimer);
  4370. Ext.tree.TreeEditor.superclass.onDestroy.call(this);
  4371. var tree = this.tree;
  4372. tree.un('beforeclick', this.beforeNodeClick, this);
  4373. tree.un('dblclick', this.onNodeDblClick, this);
  4374. }
  4375. });