26b4c576710f939bbe3a5ae3372982a8c2f500f8.svn-base 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023
  1. if (!dojo._hasResource["dijit.Tree"]) { // _hasResource checks added by build.
  2. // Do not use _hasResource directly in
  3. // your code.
  4. dojo._hasResource["dijit.Tree"] = true;
  5. dojo.provide("dijit.Tree");
  6. dojo.require("dojo.fx");
  7. dojo.require("dijit._Widget");
  8. dojo.require("dijit._Templated");
  9. dojo.require("dijit._Container");
  10. dojo.require("dojo.cookie");
  11. dojo.declare("dijit._TreeNode", [dijit._Widget, dijit._Templated,
  12. dijit._Container, dijit._Contained], {
  13. // summary
  14. // Single node within a tree
  15. // item: dojo.data.Item
  16. // the dojo.data entry this tree represents
  17. item : null,
  18. isTreeNode : true,
  19. // label: String
  20. // Text of this tree node
  21. label : "",
  22. isExpandable : null, // show expando node
  23. isExpanded : false,
  24. // state: String
  25. // dynamic loading-related stuff.
  26. // When an empty folder node appears, it is "UNCHECKED" first,
  27. // then after dojo.data query it becomes "LOADING" and, finally
  28. // "LOADED"
  29. state : "UNCHECKED",
  30. templateString : "<div class=\"dijitTreeNode dijitTreeExpandLeaf dijitTreeChildrenNo\" waiRole=\"presentation\"\n\t><span dojoAttachPoint=\"expandoNode\" class=\"dijitTreeExpando\" waiRole=\"presentation\"\n\t></span\n\t><span dojoAttachPoint=\"expandoNodeText\" class=\"dijitExpandoText\" waiRole=\"presentation\"\n\t></span\n\t>\n\t<div dojoAttachPoint=\"contentNode\" class=\"dijitTreeContent\" waiRole=\"presentation\">\n\t\t<div dojoAttachPoint=\"iconNode\" class=\"dijitInline dijitTreeIcon\" waiRole=\"presentation\"></div>\n\t\t<span dojoAttachPoint=\"labelNode\" class=\"dijitTreeLabel\" wairole=\"treeitem\" tabindex=\"-1\"></span>\n\t</div>\n</div>\n",
  31. postCreate : function() {
  32. // set label, escaping special characters
  33. this.setLabelNode(this.label);
  34. // set expand icon for leaf
  35. this._setExpando();
  36. // set icon and label class based on item
  37. this._updateItemClasses(this.item);
  38. if (this.isExpandable) {
  39. dijit.setWaiState(this.labelNode, "expanded",
  40. this.isExpanded);
  41. }
  42. },
  43. markProcessing : function() {
  44. // summary: visually denote that tree is loading data, etc.
  45. this.state = "LOADING";
  46. this._setExpando(true);
  47. },
  48. unmarkProcessing : function() {
  49. // summary: clear markup from markProcessing() call
  50. this._setExpando(false);
  51. },
  52. _updateItemClasses : function(item) {
  53. // summary: set appropriate CSS classes for item (used to
  54. // allow for item updates to change respective CSS)
  55. this.iconNode.className = "dijitInline dijitTreeIcon "
  56. + this.tree.getIconClass(item);
  57. this.labelNode.className = "dijitTreeLabel "
  58. + this.tree.getLabelClass(item);
  59. },
  60. _updateLayout : function() {
  61. // summary: set appropriate CSS classes for this.domNode
  62. var parent = this.getParent();
  63. if (parent && parent.isTree && parent._hideRoot) {
  64. /*
  65. * if we are hiding the root node then make every first
  66. * level child look like a root node
  67. */
  68. dojo.addClass(this.domNode, "dijitTreeIsRoot");
  69. } else {
  70. dojo.toggleClass(this.domNode, "dijitTreeIsLast", !this
  71. .getNextSibling());
  72. }
  73. },
  74. _setExpando : function(/* Boolean */processing) {
  75. // summary: set the right image for the expando node
  76. // apply the appropriate class to the expando node
  77. var styles = ["dijitTreeExpandoLoading",
  78. "dijitTreeExpandoOpened", "dijitTreeExpandoClosed",
  79. "dijitTreeExpandoLeaf"];
  80. var idx = processing ? 0 : (this.isExpandable
  81. ? (this.isExpanded ? 1 : 2)
  82. : 3);
  83. dojo.forEach(styles, function(s) {
  84. dojo.removeClass(this.expandoNode, s);
  85. }, this);
  86. dojo.addClass(this.expandoNode, styles[idx]);
  87. // provide a non-image based indicator for images-off mode
  88. this.expandoNodeText.innerHTML = processing
  89. ? "*"
  90. : (this.isExpandable
  91. ? (this.isExpanded ? "-" : "+")
  92. : "*");
  93. },
  94. expand : function() {
  95. // summary: show my children
  96. if (this.isExpanded) {
  97. return;
  98. }
  99. // cancel in progress collapse operation
  100. if (this._wipeOut.status() == "playing") {
  101. this._wipeOut.stop();
  102. }
  103. this.isExpanded = true;
  104. dijit.setWaiState(this.labelNode, "expanded", "true");
  105. dijit.setWaiRole(this.containerNode, "group");
  106. this._setExpando();
  107. this._wipeIn.play();
  108. },
  109. collapse : function() {
  110. if (!this.isExpanded) {
  111. return;
  112. }
  113. // cancel in progress expand operation
  114. if (this._wipeIn.status() == "playing") {
  115. this._wipeIn.stop();
  116. }
  117. this.isExpanded = false;
  118. dijit.setWaiState(this.labelNode, "expanded", "false");
  119. this._setExpando();
  120. this._wipeOut.play();
  121. },
  122. setLabelNode : function(label) {
  123. this.labelNode.innerHTML = "";
  124. this.labelNode.appendChild(document.createTextNode(label));
  125. },
  126. _setChildren : function(/* Object[] */childrenArray) {
  127. // summary:
  128. // Sets the children of this node.
  129. // Sets this.isExpandable based on whether or not there are
  130. // children
  131. // Takes array of objects like: {label: ...} (_TreeNode
  132. // options basically)
  133. // See parameters of _TreeNode for details.
  134. this.destroyDescendants();
  135. this.state = "LOADED";
  136. var nodeMap = {};
  137. if (childrenArray && childrenArray.length > 0) {
  138. this.isExpandable = true;
  139. if (!this.containerNode) { // maybe this node was
  140. // unfolderized and still
  141. // has container
  142. this.containerNode = this.tree.containerNodeTemplate
  143. .cloneNode(true);
  144. this.domNode.appendChild(this.containerNode);
  145. }
  146. // Create _TreeNode widget for each specified tree node
  147. dojo.forEach(childrenArray, function(childParams) {
  148. var child = new dijit._TreeNode(dojo.mixin(
  149. {
  150. tree : this.tree,
  151. label : this.tree
  152. .getLabel(childParams.item)
  153. }, childParams));
  154. this.addChild(child);
  155. var identity = this.tree.store
  156. .getIdentity(childParams.item);
  157. nodeMap[identity] = child;
  158. if (this.tree.persist) {
  159. if (this.tree._openedItemIds[identity]) {
  160. this.tree._expandNode(child);
  161. }
  162. }
  163. }, this);
  164. // note that updateLayout() needs to be called on each
  165. // child after
  166. // _all_ the children exist
  167. dojo.forEach(this.getChildren(), function(child, idx) {
  168. child._updateLayout();
  169. });
  170. } else {
  171. this.isExpandable = false;
  172. }
  173. if (this._setExpando) {
  174. // change expando to/form dot or + icon, as appropriate
  175. this._setExpando(false);
  176. }
  177. if (this.isTree && this._hideRoot) {
  178. // put first child in tab index if one exists.
  179. var fc = this.getChildren()[0];
  180. var tabnode = fc ? fc.labelNode : this.domNode;
  181. tabnode.setAttribute("tabIndex", "0");
  182. }
  183. // create animations for showing/hiding the children (if
  184. // children exist)
  185. if (this.containerNode && !this._wipeIn) {
  186. this._wipeIn = dojo.fx.wipeIn({
  187. node : this.containerNode,
  188. duration : 150
  189. });
  190. this._wipeOut = dojo.fx.wipeOut({
  191. node : this.containerNode,
  192. duration : 150
  193. });
  194. }
  195. return nodeMap;
  196. },
  197. _addChildren : function(/* object[] */childrenArray) {
  198. // summary:
  199. // adds the children to this node.
  200. // Takes array of objects like: {label: ...} (_TreeNode
  201. // options basically)
  202. // See parameters of _TreeNode for details.
  203. var nodeMap = {};
  204. if (childrenArray && childrenArray.length > 0) {
  205. dojo.forEach(childrenArray, function(childParams) {
  206. var child = new dijit._TreeNode(dojo.mixin({
  207. tree : this.tree,
  208. label : this.tree
  209. .getLabel(childParams.item)
  210. }, childParams));
  211. this.addChild(child);
  212. nodeMap[this.tree.store
  213. .getIdentity(childParams.item)] = child;
  214. }, this);
  215. dojo.forEach(this.getChildren(), function(child, idx) {
  216. child._updateLayout();
  217. });
  218. }
  219. return nodeMap;
  220. },
  221. deleteNode : function(/* treeNode */node) {
  222. node.destroy();
  223. var children = this.getChildren();
  224. if (children.length == 0) {
  225. this.isExpandable = false;
  226. this.collapse();
  227. }
  228. dojo.forEach(children, function(child) {
  229. child._updateLayout();
  230. });
  231. },
  232. makeExpandable : function() {
  233. // summary
  234. // if this node wasn't already showing the expando node,
  235. // turn it into one and call _setExpando()
  236. this.isExpandable = true;
  237. this._setExpando(false);
  238. }
  239. });
  240. dojo.declare("dijit.Tree", dijit._TreeNode, {
  241. // summary
  242. // This widget displays hierarchical data from a store. A query is
  243. // specified
  244. // to get the "top level children" from a data store, and then those
  245. // items are
  246. // queried for their children and so on (but lazily, as the user clicks
  247. // the expand node).
  248. //
  249. // Thus in the default mode of operation this widget is technically a
  250. // forest, not a tree,
  251. // in that there can be multiple "top level children". However, if you
  252. // specify label,
  253. // then a special top level node (not corresponding to any item in the
  254. // datastore) is
  255. // created, to father all the top level children.
  256. // store: String||dojo.data.Store
  257. // The store to get data to display in the tree
  258. store : null,
  259. // query: String
  260. // query to get top level node(s) of tree (ex: {type:'continent'})
  261. query : null,
  262. // childrenAttr: String
  263. // one ore more attributes that holds children of a tree node
  264. childrenAttr : ["children"],
  265. templateString : "<div class=\"dijitTreeContainer\" style=\"\" waiRole=\"tree\"\n\tdojoAttachEvent=\"onclick:_onClick,onkeypress:_onKeyPress\">\n\t<div class=\"dijitTreeNode dijitTreeIsRoot dijitTreeExpandLeaf dijitTreeChildrenNo\" waiRole=\"presentation\"\n\t\tdojoAttachPoint=\"rowNode\"\n\t\t><span dojoAttachPoint=\"expandoNode\" class=\"dijitTreeExpando\" waiRole=\"presentation\"\n\t\t></span\n\t\t><span dojoAttachPoint=\"expandoNodeText\" class=\"dijitExpandoText\" waiRole=\"presentation\"\n\t\t></span\n\t\t>\n\t\t<div dojoAttachPoint=\"contentNode\" class=\"dijitTreeContent\" waiRole=\"presentation\">\n\t\t\t<div dojoAttachPoint=\"iconNode\" class=\"dijitInline dijitTreeIcon\" waiRole=\"presentation\"></div>\n\t\t\t<span dojoAttachPoint=\"labelNode\" class=\"dijitTreeLabel\" wairole=\"treeitem\" tabindex=\"0\"></span>\n\t\t</div>\n\t</div>\n</div>\n",
  266. isExpandable : true,
  267. isTree : true,
  268. // persist: Boolean
  269. // enables/disables use of cookies for state saving.
  270. persist : true,
  271. // dndController: String
  272. // class name to use as as the dnd controller
  273. dndController : null,
  274. // parameters to pull off of the tree and pass on to the dndController
  275. // as its params
  276. dndParams : ["onDndDrop", "itemCreator", "onDndCancel",
  277. "checkAcceptance", "checkItemAcceptance"],
  278. // declare the above items so they can be pulled from the tree's markup
  279. onDndDrop : null,
  280. itemCreator : null,
  281. onDndCancel : null,
  282. checkAcceptance : null,
  283. checkItemAcceptance : null,
  284. _publish : function(/* String */topicName, /* Object */message) {
  285. // summary:
  286. // Publish a message for this widget/topic
  287. dojo.publish(this.id, [dojo.mixin({
  288. tree : this,
  289. event : topicName
  290. }, message || {})]);
  291. },
  292. postMixInProperties : function() {
  293. this.tree = this;
  294. this.lastFocused = this.labelNode;
  295. this._itemNodeMap = {};
  296. this._hideRoot = !this.label;
  297. if (!this.store.getFeatures()['dojo.data.api.Identity']) {
  298. throw new Error("dijit.tree requires access to a store supporting the dojo.data Identity api");
  299. }
  300. if (!this.cookieName) {
  301. this.cookieName = this.id + "SaveStateCookie";
  302. }
  303. // if the store supports Notification, subscribe to the notification
  304. // events
  305. if (this.store.getFeatures()['dojo.data.api.Notification']) {
  306. this.connect(this.store, "onNew", "_onNewItem");
  307. this.connect(this.store, "onDelete", "_onDeleteItem");
  308. this.connect(this.store, "onSet", "_onSetItem");
  309. }
  310. },
  311. postCreate : function() {
  312. // load in which nodes should be opened automatically
  313. if (this.persist) {
  314. var cookie = dojo.cookie(this.cookieName);
  315. this._openedItemIds = {};
  316. if (cookie) {
  317. dojo.forEach(cookie.split(','), function(item) {
  318. this._openedItemIds[item] = true;
  319. }, this);
  320. }
  321. }
  322. // make template for container node (we will clone this and insert
  323. // it into
  324. // any nodes that have children)
  325. var div = document.createElement('div');
  326. div.style.display = 'none';
  327. div.className = "dijitTreeContainer";
  328. dijit.setWaiRole(div, "presentation");
  329. this.containerNodeTemplate = div;
  330. if (this._hideRoot) {
  331. this.rowNode.style.display = "none";
  332. }
  333. this.inherited("postCreate", arguments);
  334. // load top level children
  335. this._expandNode(this);
  336. if (this.dndController) {
  337. if (dojo.isString(this.dndController)) {
  338. this.dndController = dojo.getObject(this.dndController);
  339. }
  340. var params = {};
  341. for (var i = 0; i < this.dndParams.length; i++) {
  342. if (this[this.dndParams[i]]) {
  343. params[this.dndParams[i]] = this[this.dndParams[i]];
  344. }
  345. }
  346. this.dndController = new this.dndController(this, params);
  347. }
  348. this.connect(this.domNode, dojo.isIE ? "onactivate" : "onfocus",
  349. "_onTreeFocus");
  350. },
  351. // //////////// Data store related functions //////////////////////
  352. mayHaveChildren : function(/* dojo.data.Item */item) {
  353. // summary
  354. // User overridable function to tell if an item has or may have
  355. // children.
  356. // Controls whether or not +/- expando icon is shown.
  357. // (For efficiency reasons we may not want to check if an element
  358. // has
  359. // children until user clicks the expando node)
  360. return dojo.some(this.childrenAttr, function(attr) {
  361. return this.store.hasAttribute(item, attr);
  362. }, this);
  363. },
  364. getItemChildren : function(/* dojo.data.Item */parentItem, /* function(items) */
  365. onComplete) {
  366. // summary
  367. // User overridable function that return array of child items of
  368. // given parent item,
  369. // or if parentItem==null then return top items in tree
  370. var store = this.store;
  371. if (parentItem == null) {
  372. // get top level nodes
  373. store.fetch({
  374. query : this.query,
  375. onComplete : onComplete
  376. });
  377. } else {
  378. // get children of specified node
  379. var childItems = [];
  380. for (var i = 0; i < this.childrenAttr.length; i++) {
  381. childItems = childItems.concat(store.getValues(parentItem,
  382. this.childrenAttr[i]));
  383. }
  384. // count how many items need to be loaded
  385. var _waitCount = 0;
  386. dojo.forEach(childItems, function(item) {
  387. if (!store.isItemLoaded(item)) {
  388. _waitCount++;
  389. }
  390. });
  391. if (_waitCount == 0) {
  392. // all items are already loaded. proceed..
  393. onComplete(childItems);
  394. } else {
  395. // still waiting for some or all of the items to load
  396. function onItem(item) {
  397. if (--_waitCount == 0) {
  398. // all nodes have been loaded, send them to the tree
  399. onComplete(childItems);
  400. }
  401. }
  402. dojo.forEach(childItems, function(item) {
  403. if (!store.isItemLoaded(item)) {
  404. store.loadItem({
  405. item : item,
  406. onItem : onItem
  407. });
  408. }
  409. });
  410. }
  411. }
  412. },
  413. getItemParentIdentity : function(/* dojo.data.Item */item, /* Object */
  414. parentInfo) {
  415. // summary
  416. // User overridable function, to return id of parent (or null if top
  417. // level).
  418. // It's called with args from dojo.store.onNew
  419. return this.store.getIdentity(parentInfo.item); // String
  420. },
  421. getLabel : function(/* dojo.data.Item */item) {
  422. // summary: user overridable function to get the label for a tree
  423. // node (given the item)
  424. return this.store.getLabel(item); // String
  425. },
  426. getIconClass : function(/* dojo.data.Item */item) {
  427. // summary: user overridable function to return CSS class name to
  428. // display icon
  429. },
  430. getLabelClass : function(/* dojo.data.Item */item) {
  431. // summary: user overridable function to return CSS class name to
  432. // display label
  433. },
  434. _onLoadAllItems : function(/* _TreeNode */node, /* dojo.data.Item[] */
  435. items) {
  436. // sumary: callback when all the children of a given node have been
  437. // loaded
  438. var childParams = dojo.map(items, function(item) {
  439. return {
  440. item : item,
  441. isExpandable : this.mayHaveChildren(item)
  442. };
  443. }, this);
  444. dojo.mixin(this._itemNodeMap, node._setChildren(childParams));
  445. this._expandNode(node);
  446. },
  447. // ///////// Keyboard and Mouse handlers ////////////////////
  448. _onKeyPress : function(/* Event */e) {
  449. // summary: translates keypress events into commands for the
  450. // controller
  451. if (e.altKey) {
  452. return;
  453. }
  454. var treeNode = dijit.getEnclosingWidget(e.target);
  455. if (!treeNode) {
  456. return;
  457. }
  458. // Note: On IE e.keyCode is not 0 for printables so check
  459. // e.charCode.
  460. // In dojo charCode is universally 0 for non-printables.
  461. if (e.charCode) { // handle printables (letter navigation)
  462. // Check for key navigation.
  463. var navKey = e.charCode;
  464. if (!e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey) {
  465. navKey = (String.fromCharCode(navKey)).toLowerCase();
  466. this._onLetterKeyNav({
  467. node : treeNode,
  468. key : navKey
  469. });
  470. dojo.stopEvent(e);
  471. }
  472. } else { // handle non-printables (arrow keys)
  473. var map = this._keyHandlerMap;
  474. if (!map) {
  475. // setup table mapping keys to events
  476. map = {};
  477. map[dojo.keys.ENTER] = "_onEnterKey";
  478. map[dojo.keys.LEFT_ARROW] = "_onLeftArrow";
  479. map[dojo.keys.RIGHT_ARROW] = "_onRightArrow";
  480. map[dojo.keys.UP_ARROW] = "_onUpArrow";
  481. map[dojo.keys.DOWN_ARROW] = "_onDownArrow";
  482. map[dojo.keys.HOME] = "_onHomeKey";
  483. map[dojo.keys.END] = "_onEndKey";
  484. this._keyHandlerMap = map;
  485. }
  486. if (this._keyHandlerMap[e.keyCode]) {
  487. this[this._keyHandlerMap[e.keyCode]]({
  488. node : treeNode,
  489. item : treeNode.item
  490. });
  491. dojo.stopEvent(e);
  492. }
  493. }
  494. },
  495. _onEnterKey : function(/* Object */message) {
  496. this._publish("execute", {
  497. item : message.item,
  498. node : message.node
  499. });
  500. this.onClick(message.item, message.node);
  501. },
  502. _onDownArrow : function(/* Object */message) {
  503. // summary: down arrow pressed; get next visible node, set focus
  504. // there
  505. var returnNode = this._navToNextNode(message.node);
  506. if (returnNode && returnNode.isTreeNode) {
  507. returnNode.tree.focusNode(returnNode);
  508. return returnNode;
  509. }
  510. },
  511. _onUpArrow : function(/* Object */message) {
  512. // summary: up arrow pressed; move to previous visible node
  513. var nodeWidget = message.node;
  514. var returnWidget = nodeWidget;
  515. // if younger siblings
  516. var previousSibling = nodeWidget.getPreviousSibling();
  517. if (previousSibling) {
  518. nodeWidget = previousSibling;
  519. // if the previous nodeWidget is expanded, dive in deep
  520. while (nodeWidget.isExpandable && nodeWidget.isExpanded
  521. && nodeWidget.hasChildren()) {
  522. returnWidget = nodeWidget;
  523. // move to the last child
  524. var children = nodeWidget.getChildren();
  525. nodeWidget = children[children.length - 1];
  526. }
  527. } else {
  528. // if this is the first child, return the parent
  529. // unless the parent is the root of a tree with a hidden root
  530. var parent = nodeWidget.getParent();
  531. if (!(this._hideRoot && parent === this)) {
  532. nodeWidget = parent;
  533. }
  534. }
  535. if (nodeWidget && nodeWidget.isTreeNode) {
  536. returnWidget = nodeWidget;
  537. }
  538. if (returnWidget && returnWidget.isTreeNode) {
  539. returnWidget.tree.focusNode(returnWidget);
  540. return returnWidget;
  541. }
  542. },
  543. _onRightArrow : function(/* Object */message) {
  544. // summary: right arrow pressed; go to child node
  545. var nodeWidget = message.node;
  546. var returnWidget = nodeWidget;
  547. // if not expanded, expand, else move to 1st child
  548. if (nodeWidget.isExpandable && !nodeWidget.isExpanded) {
  549. this._expandNode(nodeWidget);
  550. } else if (nodeWidget.hasChildren()) {
  551. nodeWidget = nodeWidget.getChildren()[0];
  552. }
  553. if (nodeWidget && nodeWidget.isTreeNode) {
  554. returnWidget = nodeWidget;
  555. }
  556. if (returnWidget && returnWidget.isTreeNode) {
  557. returnWidget.tree.focusNode(returnWidget);
  558. return returnWidget;
  559. }
  560. },
  561. _onLeftArrow : function(/* Object */message) {
  562. // summary: left arrow pressed; go to parent
  563. var node = message.node;
  564. var returnWidget = node;
  565. // if not collapsed, collapse, else move to parent
  566. if (node.isExpandable && node.isExpanded) {
  567. this._collapseNode(node);
  568. } else {
  569. node = node.getParent();
  570. }
  571. if (node && node.isTreeNode) {
  572. returnWidget = node;
  573. }
  574. if (returnWidget && returnWidget.isTreeNode) {
  575. returnWidget.tree.focusNode(returnWidget);
  576. return returnWidget;
  577. }
  578. },
  579. _onHomeKey : function() {
  580. // summary: home pressed; get first visible node, set focus there
  581. var returnNode = this._navToRootOrFirstNode();
  582. if (returnNode) {
  583. returnNode.tree.focusNode(returnNode);
  584. return returnNode;
  585. }
  586. },
  587. _onEndKey : function(/* Object */message) {
  588. // summary: end pressed; go to last visible node
  589. var returnWidget = message.node.tree;
  590. var lastChild = returnWidget;
  591. while (lastChild.isExpanded) {
  592. var c = lastChild.getChildren();
  593. lastChild = c[c.length - 1];
  594. if (lastChild.isTreeNode) {
  595. returnWidget = lastChild;
  596. }
  597. }
  598. if (returnWidget && returnWidget.isTreeNode) {
  599. returnWidget.tree.focusNode(returnWidget);
  600. return returnWidget;
  601. }
  602. },
  603. _onLetterKeyNav : function(message) {
  604. // summary: letter key pressed; search for node starting with first
  605. // char = key
  606. var node = startNode = message.node;
  607. var key = message.key;
  608. do {
  609. node = this._navToNextNode(node);
  610. // check for last node, jump to first node if necessary
  611. if (!node) {
  612. node = this._navToRootOrFirstNode();
  613. }
  614. } while (node !== startNode
  615. && (node.label.charAt(0).toLowerCase() != key));
  616. if (node && node.isTreeNode) {
  617. // no need to set focus if back where we started
  618. if (node !== startNode) {
  619. node.tree.focusNode(node);
  620. }
  621. return node;
  622. }
  623. },
  624. _onClick : function(/* Event */e) {
  625. // summary: translates click events into commands for the controller
  626. // to process
  627. var domElement = e.target;
  628. // find node
  629. var nodeWidget = dijit.getEnclosingWidget(domElement);
  630. if (!nodeWidget || !nodeWidget.isTreeNode) {
  631. return;
  632. }
  633. if (domElement == nodeWidget.expandoNode
  634. || domElement == nodeWidget.expandoNodeText) {
  635. // expando node was clicked
  636. if (nodeWidget.isExpandable) {
  637. this._onExpandoClick({
  638. node : nodeWidget
  639. });
  640. }
  641. } else {
  642. this._publish("execute", {
  643. item : nodeWidget.item,
  644. node : nodeWidget
  645. });
  646. this.onClick(nodeWidget.item, nodeWidget);
  647. this.focusNode(nodeWidget);
  648. }
  649. dojo.stopEvent(e);
  650. },
  651. _onExpandoClick : function(/* Object */message) {
  652. // summary: user clicked the +/- icon; expand or collapse my
  653. // children.
  654. var node = message.node;
  655. if (node.isExpanded) {
  656. this._collapseNode(node);
  657. } else {
  658. this._expandNode(node);
  659. }
  660. },
  661. onClick : function(/* dojo.data */item, /* TreeNode */node) {
  662. // summary: user overridable function for executing a tree item
  663. },
  664. _navToNextNode : function(node) {
  665. // summary: get next visible node
  666. var returnNode;
  667. // if this is an expanded node, get the first child
  668. if (node.isExpandable && node.isExpanded && node.hasChildren()) {
  669. returnNode = node.getChildren()[0];
  670. } else {
  671. // find a parent node with a sibling
  672. while (node && node.isTreeNode) {
  673. returnNode = node.getNextSibling();
  674. if (returnNode) {
  675. break;
  676. }
  677. node = node.getParent();
  678. }
  679. }
  680. return returnNode;
  681. },
  682. _navToRootOrFirstNode : function() {
  683. // summary: get first visible node
  684. if (!this._hideRoot) {
  685. return this;
  686. } else {
  687. var returnNode = this.getChildren()[0];
  688. if (returnNode && returnNode.isTreeNode) {
  689. return returnNode;
  690. }
  691. }
  692. },
  693. _collapseNode : function(/* _TreeNode */node) {
  694. // summary: called when the user has requested to collapse the node
  695. if (node.isExpandable) {
  696. if (node.state == "LOADING") {
  697. // ignore clicks while we are in the process of loading data
  698. return;
  699. }
  700. if (this.lastFocused) {
  701. // are we collapsing a descendant with focus?
  702. if (dojo.isDescendant(this.lastFocused.domNode,
  703. node.domNode)) {
  704. this.focusNode(node);
  705. } else {
  706. // clicking the expando node might have erased focus
  707. // from
  708. // the current item; restore it
  709. this.focusNode(this.lastFocused);
  710. }
  711. }
  712. node.collapse();
  713. if (this.persist && node.item) {
  714. delete this._openedItemIds[this.store
  715. .getIdentity(node.item)];
  716. this._saveState();
  717. }
  718. }
  719. },
  720. _expandNode : function(/* _TreeNode */node) {
  721. // summary: called when the user has requested to expand the node
  722. // clicking the expando node might have erased focus from the
  723. // current item; restore it
  724. var t = node.tree;
  725. if (t.lastFocused) {
  726. t.focusNode(t.lastFocused);
  727. }
  728. if (!node.isExpandable) {
  729. return;
  730. }
  731. var store = this.store;
  732. var getValue = this.store.getValue;
  733. switch (node.state) {
  734. case "LOADING" :
  735. // ignore clicks while we are in the process of loading data
  736. return;
  737. case "UNCHECKED" :
  738. // need to load all the children, and then expand
  739. node.markProcessing();
  740. var _this = this;
  741. var onComplete = function(childItems) {
  742. node.unmarkProcessing();
  743. _this._onLoadAllItems(node, childItems);
  744. };
  745. this.getItemChildren(node.item, onComplete);
  746. break;
  747. default :
  748. // data is already loaded; just proceed
  749. if (node.expand) { // top level Tree doesn't have expand()
  750. // method
  751. node.expand();
  752. if (this.persist && node.item) {
  753. this._openedItemIds[this.store
  754. .getIdentity(node.item)] = true;
  755. this._saveState();
  756. }
  757. }
  758. break;
  759. }
  760. },
  761. // //////////////// Miscellaneous functions ////////////////
  762. blurNode : function() {
  763. // summary
  764. // Removes focus from the currently focused node (which must be
  765. // visible).
  766. // Usually not called directly (just call focusNode() on another
  767. // node instead)
  768. var node = this.lastFocused;
  769. if (!node) {
  770. return;
  771. }
  772. var labelNode = node.labelNode;
  773. dojo.removeClass(labelNode, "dijitTreeLabelFocused");
  774. labelNode.setAttribute("tabIndex", "-1");
  775. this.lastFocused = null;
  776. },
  777. focusNode : function(/* _tree.Node */node) {
  778. // summary
  779. // Focus on the specified node (which must be visible)
  780. // set focus so that the label will be voiced using screen readers
  781. node.labelNode.focus();
  782. },
  783. _onBlur : function() {
  784. // summary:
  785. // We've moved away from the whole tree. The currently "focused"
  786. // node
  787. // (see focusNode above) should remain as the lastFocused node so we
  788. // can
  789. // tab back into the tree. Just change CSS to get rid of the dotted
  790. // border
  791. // until that time
  792. if (this.lastFocused) {
  793. var labelNode = this.lastFocused.labelNode;
  794. dojo.removeClass(labelNode, "dijitTreeLabelFocused");
  795. }
  796. },
  797. _onTreeFocus : function(evt) {
  798. var node = dijit.getEnclosingWidget(evt.target);
  799. if (node != this.lastFocused) {
  800. this.blurNode();
  801. }
  802. var labelNode = node.labelNode;
  803. // set tabIndex so that the tab key can find this node
  804. labelNode.setAttribute("tabIndex", "0");
  805. dojo.addClass(labelNode, "dijitTreeLabelFocused");
  806. this.lastFocused = node;
  807. },
  808. // ////////////// Events from data store //////////////////////////
  809. _onNewItem : function(/* Object */item, parentInfo) {
  810. // summary: callback when new item has been added to the store.
  811. var loadNewItem; // should new item be displayed in tree?
  812. if (parentInfo) {
  813. var parent = this._itemNodeMap[this.getItemParentIdentity(item,
  814. parentInfo)];
  815. // If new item's parent item not in tree view yet, can safely
  816. // ignore.
  817. // Also, if a query of specified parent wouldn't return this
  818. // item, then ignore.
  819. if (!parent
  820. || dojo
  821. .indexOf(this.childrenAttr,
  822. parentInfo.attribute) == -1) {
  823. return;
  824. }
  825. }
  826. var childParams = {
  827. item : item,
  828. isExpandable : this.mayHaveChildren(item)
  829. };
  830. if (parent) {
  831. if (!parent.isExpandable) {
  832. parent.makeExpandable();
  833. }
  834. if (parent.state == "LOADED" || parent.isExpanded) {
  835. var childrenMap = parent._addChildren([childParams]);
  836. }
  837. } else {
  838. // top level node
  839. var childrenMap = this._addChildren([childParams]);
  840. }
  841. if (childrenMap) {
  842. dojo.mixin(this._itemNodeMap, childrenMap);
  843. // this._itemNodeMap[this.store.getIdentity(item)]=child;
  844. }
  845. },
  846. _onDeleteItem : function(/* Object */item) {
  847. // summary: delete event from the store
  848. // since the object has just been deleted, we need to
  849. // use the name directly
  850. var identity = this.store.getIdentity(item);
  851. var node = this._itemNodeMap[identity];
  852. if (node) {
  853. var parent = node.getParent();
  854. parent.deleteNode(node);
  855. this._itemNodeMap[identity] = null;
  856. }
  857. },
  858. _onSetItem : function(/* Object */item) {
  859. // summary: set data event on an item in the store
  860. var identity = this.store.getIdentity(item);
  861. node = this._itemNodeMap[identity];
  862. if (node) {
  863. node.setLabelNode(this.getLabel(item));
  864. node._updateItemClasses(item);
  865. }
  866. },
  867. _saveState : function() {
  868. // summary: create and save a cookie with the currently expanded
  869. // nodes identifiers
  870. if (!this.persist) {
  871. return;
  872. }
  873. var ary = [];
  874. for (var id in this._openedItemIds) {
  875. ary.push(id);
  876. }
  877. dojo.cookie(this.cookieName, ary.join(","));
  878. }
  879. });
  880. }