treeoutline.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947
  1. /*
  2. * Copyright (C) 2007 Apple Inc. All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions
  6. * are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * 2. Redistributions in binary form must reproduce the above copyright
  11. * notice, this list of conditions and the following disclaimer in the
  12. * documentation and/or other materials provided with the distribution.
  13. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
  14. * its contributors may be used to endorse or promote products derived
  15. * from this software without specific prior written permission.
  16. *
  17. * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
  18. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  19. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  20. * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
  21. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  22. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  23. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  24. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  26. * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27. */
  28. /**
  29. * @constructor
  30. * @param {Element} listNode
  31. * @param {boolean=} nonFocusable
  32. */
  33. function TreeOutline(listNode, nonFocusable)
  34. {
  35. /** @type {!Array.<TreeElement>} */
  36. this.children = [];
  37. this.selectedTreeElement = null;
  38. this._childrenListNode = listNode;
  39. this.childrenListElement = this._childrenListNode;
  40. this._childrenListNode.removeChildren();
  41. this.expandTreeElementsWhenArrowing = false;
  42. this.root = true;
  43. this.hasChildren = false;
  44. this.expanded = true;
  45. this.selected = false;
  46. this.treeOutline = this;
  47. /** @type {function(TreeElement,TreeElement):number|null} */
  48. this.comparator = null;
  49. this.setFocusable(!nonFocusable);
  50. this._childrenListNode.addEventListener("keydown", this._treeKeyDown.bind(this), true);
  51. /** @type {!Map.<!Object, !Array.<!TreeElement>>} */
  52. this._treeElementsMap = new Map();
  53. /** @type {!Map.<!Object, boolean>} */
  54. this._expandedStateMap = new Map();
  55. }
  56. TreeOutline.prototype.setFocusable = function(focusable)
  57. {
  58. if (focusable)
  59. this._childrenListNode.setAttribute("tabIndex", 0);
  60. else
  61. this._childrenListNode.removeAttribute("tabIndex");
  62. }
  63. /**
  64. * @param {TreeElement} child
  65. */
  66. TreeOutline.prototype.appendChild = function(child)
  67. {
  68. var insertionIndex;
  69. if (this.treeOutline.comparator)
  70. insertionIndex = insertionIndexForObjectInListSortedByFunction(child, this.children, this.treeOutline.comparator);
  71. else
  72. insertionIndex = this.children.length;
  73. this.insertChild(child, insertionIndex);
  74. }
  75. /**
  76. * @param {TreeElement} child
  77. * @param {TreeElement} beforeChild
  78. */
  79. TreeOutline.prototype.insertBeforeChild = function(child, beforeChild)
  80. {
  81. if (!child)
  82. throw("child can't be undefined or null");
  83. if (!beforeChild)
  84. throw("beforeChild can't be undefined or null");
  85. var childIndex = this.children.indexOf(beforeChild);
  86. if (childIndex === -1)
  87. throw("beforeChild not found in this node's children");
  88. this.insertChild(child, childIndex);
  89. }
  90. /**
  91. * @param {TreeElement} child
  92. * @param {number} index
  93. */
  94. TreeOutline.prototype.insertChild = function(child, index)
  95. {
  96. if (!child)
  97. throw("child can't be undefined or null");
  98. var previousChild = (index > 0 ? this.children[index - 1] : null);
  99. if (previousChild) {
  100. previousChild.nextSibling = child;
  101. child.previousSibling = previousChild;
  102. } else {
  103. child.previousSibling = null;
  104. }
  105. var nextChild = this.children[index];
  106. if (nextChild) {
  107. nextChild.previousSibling = child;
  108. child.nextSibling = nextChild;
  109. } else {
  110. child.nextSibling = null;
  111. }
  112. this.children.splice(index, 0, child);
  113. this.hasChildren = true;
  114. child.parent = this;
  115. child.treeOutline = this.treeOutline;
  116. child.treeOutline._rememberTreeElement(child);
  117. var current = child.children[0];
  118. while (current) {
  119. current.treeOutline = this.treeOutline;
  120. current.treeOutline._rememberTreeElement(current);
  121. current = current.traverseNextTreeElement(false, child, true);
  122. }
  123. if (child.hasChildren && typeof(child.treeOutline._expandedStateMap.get(child.representedObject)) !== "undefined")
  124. child.expanded = child.treeOutline._expandedStateMap.get(child.representedObject);
  125. if (!this._childrenListNode) {
  126. this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
  127. this._childrenListNode.parentTreeElement = this;
  128. this._childrenListNode.classList.add("children");
  129. if (this.hidden)
  130. this._childrenListNode.classList.add("hidden");
  131. }
  132. child._attach();
  133. }
  134. /**
  135. * @param {number} childIndex
  136. */
  137. TreeOutline.prototype.removeChildAtIndex = function(childIndex)
  138. {
  139. if (childIndex < 0 || childIndex >= this.children.length)
  140. throw("childIndex out of range");
  141. var child = this.children[childIndex];
  142. this.children.splice(childIndex, 1);
  143. var parent = child.parent;
  144. if (child.deselect()) {
  145. if (child.previousSibling)
  146. child.previousSibling.select();
  147. else if (child.nextSibling)
  148. child.nextSibling.select();
  149. else
  150. parent.select();
  151. }
  152. if (child.previousSibling)
  153. child.previousSibling.nextSibling = child.nextSibling;
  154. if (child.nextSibling)
  155. child.nextSibling.previousSibling = child.previousSibling;
  156. if (child.treeOutline) {
  157. child.treeOutline._forgetTreeElement(child);
  158. child.treeOutline._forgetChildrenRecursive(child);
  159. }
  160. child._detach();
  161. child.treeOutline = null;
  162. child.parent = null;
  163. child.nextSibling = null;
  164. child.previousSibling = null;
  165. }
  166. /**
  167. * @param {TreeElement} child
  168. */
  169. TreeOutline.prototype.removeChild = function(child)
  170. {
  171. if (!child)
  172. throw("child can't be undefined or null");
  173. var childIndex = this.children.indexOf(child);
  174. if (childIndex === -1)
  175. throw("child not found in this node's children");
  176. this.removeChildAtIndex.call(this, childIndex);
  177. }
  178. TreeOutline.prototype.removeChildren = function()
  179. {
  180. for (var i = 0; i < this.children.length; ++i) {
  181. var child = this.children[i];
  182. child.deselect();
  183. if (child.treeOutline) {
  184. child.treeOutline._forgetTreeElement(child);
  185. child.treeOutline._forgetChildrenRecursive(child);
  186. }
  187. child._detach();
  188. child.treeOutline = null;
  189. child.parent = null;
  190. child.nextSibling = null;
  191. child.previousSibling = null;
  192. }
  193. this.children = [];
  194. }
  195. /**
  196. * @param {TreeElement} element
  197. */
  198. TreeOutline.prototype._rememberTreeElement = function(element)
  199. {
  200. if (!this._treeElementsMap.get(element.representedObject))
  201. this._treeElementsMap.put(element.representedObject, []);
  202. // check if the element is already known
  203. var elements = this._treeElementsMap.get(element.representedObject);
  204. if (elements.indexOf(element) !== -1)
  205. return;
  206. // add the element
  207. elements.push(element);
  208. }
  209. /**
  210. * @param {TreeElement} element
  211. */
  212. TreeOutline.prototype._forgetTreeElement = function(element)
  213. {
  214. if (this._treeElementsMap.get(element.representedObject)) {
  215. var elements = this._treeElementsMap.get(element.representedObject);
  216. elements.remove(element, true);
  217. if (!elements.length)
  218. this._treeElementsMap.remove(element.representedObject);
  219. }
  220. }
  221. /**
  222. * @param {TreeElement} parentElement
  223. */
  224. TreeOutline.prototype._forgetChildrenRecursive = function(parentElement)
  225. {
  226. var child = parentElement.children[0];
  227. while (child) {
  228. this._forgetTreeElement(child);
  229. child = child.traverseNextTreeElement(false, parentElement, true);
  230. }
  231. }
  232. /**
  233. * @param {Object} representedObject
  234. * @return {TreeElement}
  235. */
  236. TreeOutline.prototype.getCachedTreeElement = function(representedObject)
  237. {
  238. if (!representedObject)
  239. return null;
  240. var elements = this._treeElementsMap.get(representedObject);
  241. if (elements && elements.length)
  242. return elements[0];
  243. return null;
  244. }
  245. /**
  246. * @param {Object} representedObject
  247. * @return {TreeElement}
  248. */
  249. TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor, getParent)
  250. {
  251. if (!representedObject)
  252. return null;
  253. var cachedElement = this.getCachedTreeElement(representedObject);
  254. if (cachedElement)
  255. return cachedElement;
  256. // Walk up the parent pointers from the desired representedObject
  257. var ancestors = [];
  258. for (var currentObject = getParent(representedObject); currentObject; currentObject = getParent(currentObject)) {
  259. ancestors.push(currentObject);
  260. if (this.getCachedTreeElement(currentObject)) // stop climbing as soon as we hit
  261. break;
  262. }
  263. if (!currentObject)
  264. return null;
  265. // Walk down to populate each ancestor's children, to fill in the tree and the cache.
  266. for (var i = ancestors.length - 1; i >= 0; --i) {
  267. var treeElement = this.getCachedTreeElement(ancestors[i]);
  268. if (treeElement)
  269. treeElement.onpopulate(); // fill the cache with the children of treeElement
  270. }
  271. return this.getCachedTreeElement(representedObject);
  272. }
  273. /**
  274. * @param {number} x
  275. * @param {number} y
  276. * @return {TreeElement}
  277. */
  278. TreeOutline.prototype.treeElementFromPoint = function(x, y)
  279. {
  280. var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y);
  281. if (!node)
  282. return null;
  283. var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]);
  284. if (listNode)
  285. return listNode.parentTreeElement || listNode.treeElement;
  286. return null;
  287. }
  288. TreeOutline.prototype._treeKeyDown = function(event)
  289. {
  290. if (event.target !== this._childrenListNode)
  291. return;
  292. if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey)
  293. return;
  294. var handled = false;
  295. var nextSelectedElement;
  296. if (event.keyIdentifier === "Up" && !event.altKey) {
  297. nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true);
  298. while (nextSelectedElement && !nextSelectedElement.selectable)
  299. nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing);
  300. handled = nextSelectedElement ? true : false;
  301. } else if (event.keyIdentifier === "Down" && !event.altKey) {
  302. nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true);
  303. while (nextSelectedElement && !nextSelectedElement.selectable)
  304. nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing);
  305. handled = nextSelectedElement ? true : false;
  306. } else if (event.keyIdentifier === "Left") {
  307. if (this.selectedTreeElement.expanded) {
  308. if (event.altKey)
  309. this.selectedTreeElement.collapseRecursively();
  310. else
  311. this.selectedTreeElement.collapse();
  312. handled = true;
  313. } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) {
  314. handled = true;
  315. if (this.selectedTreeElement.parent.selectable) {
  316. nextSelectedElement = this.selectedTreeElement.parent;
  317. while (nextSelectedElement && !nextSelectedElement.selectable)
  318. nextSelectedElement = nextSelectedElement.parent;
  319. handled = nextSelectedElement ? true : false;
  320. } else if (this.selectedTreeElement.parent)
  321. this.selectedTreeElement.parent.collapse();
  322. }
  323. } else if (event.keyIdentifier === "Right") {
  324. if (!this.selectedTreeElement.revealed()) {
  325. this.selectedTreeElement.reveal();
  326. handled = true;
  327. } else if (this.selectedTreeElement.hasChildren) {
  328. handled = true;
  329. if (this.selectedTreeElement.expanded) {
  330. nextSelectedElement = this.selectedTreeElement.children[0];
  331. while (nextSelectedElement && !nextSelectedElement.selectable)
  332. nextSelectedElement = nextSelectedElement.nextSibling;
  333. handled = nextSelectedElement ? true : false;
  334. } else {
  335. if (event.altKey)
  336. this.selectedTreeElement.expandRecursively();
  337. else
  338. this.selectedTreeElement.expand();
  339. }
  340. }
  341. } else if (event.keyCode === 8 /* Backspace */ || event.keyCode === 46 /* Delete */)
  342. handled = this.selectedTreeElement.ondelete();
  343. else if (isEnterKey(event))
  344. handled = this.selectedTreeElement.onenter();
  345. else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Space.code)
  346. handled = this.selectedTreeElement.onspace();
  347. if (nextSelectedElement) {
  348. nextSelectedElement.reveal();
  349. nextSelectedElement.select(false, true);
  350. }
  351. if (handled)
  352. event.consume(true);
  353. }
  354. TreeOutline.prototype.expand = function()
  355. {
  356. // this is the root, do nothing
  357. }
  358. TreeOutline.prototype.collapse = function()
  359. {
  360. // this is the root, do nothing
  361. }
  362. TreeOutline.prototype.revealed = function()
  363. {
  364. return true;
  365. }
  366. TreeOutline.prototype.reveal = function()
  367. {
  368. // this is the root, do nothing
  369. }
  370. TreeOutline.prototype.select = function()
  371. {
  372. // this is the root, do nothing
  373. }
  374. /**
  375. * @param {boolean=} omitFocus
  376. */
  377. TreeOutline.prototype.revealAndSelect = function(omitFocus)
  378. {
  379. // this is the root, do nothing
  380. }
  381. /**
  382. * @constructor
  383. * @param {Object=} representedObject
  384. * @param {boolean=} hasChildren
  385. */
  386. function TreeElement(title, representedObject, hasChildren)
  387. {
  388. this._title = title;
  389. this.representedObject = (representedObject || {});
  390. this._hidden = false;
  391. this._selectable = true;
  392. this.expanded = false;
  393. this.selected = false;
  394. this.hasChildren = hasChildren;
  395. this.children = [];
  396. this.treeOutline = null;
  397. this.parent = null;
  398. this.previousSibling = null;
  399. this.nextSibling = null;
  400. this._listItemNode = null;
  401. }
  402. TreeElement.prototype = {
  403. arrowToggleWidth: 10,
  404. get selectable() {
  405. if (this._hidden)
  406. return false;
  407. return this._selectable;
  408. },
  409. set selectable(x) {
  410. this._selectable = x;
  411. },
  412. get listItemElement() {
  413. return this._listItemNode;
  414. },
  415. get childrenListElement() {
  416. return this._childrenListNode;
  417. },
  418. get title() {
  419. return this._title;
  420. },
  421. set title(x) {
  422. this._title = x;
  423. this._setListItemNodeContent();
  424. },
  425. get tooltip() {
  426. return this._tooltip;
  427. },
  428. set tooltip(x) {
  429. this._tooltip = x;
  430. if (this._listItemNode)
  431. this._listItemNode.title = x ? x : "";
  432. },
  433. get hasChildren() {
  434. return this._hasChildren;
  435. },
  436. set hasChildren(x) {
  437. if (this._hasChildren === x)
  438. return;
  439. this._hasChildren = x;
  440. if (!this._listItemNode)
  441. return;
  442. if (x)
  443. this._listItemNode.classList.add("parent");
  444. else {
  445. this._listItemNode.classList.remove("parent");
  446. this.collapse();
  447. }
  448. },
  449. get hidden() {
  450. return this._hidden;
  451. },
  452. set hidden(x) {
  453. if (this._hidden === x)
  454. return;
  455. this._hidden = x;
  456. if (x) {
  457. if (this._listItemNode)
  458. this._listItemNode.classList.add("hidden");
  459. if (this._childrenListNode)
  460. this._childrenListNode.classList.add("hidden");
  461. } else {
  462. if (this._listItemNode)
  463. this._listItemNode.classList.remove("hidden");
  464. if (this._childrenListNode)
  465. this._childrenListNode.classList.remove("hidden");
  466. }
  467. },
  468. get shouldRefreshChildren() {
  469. return this._shouldRefreshChildren;
  470. },
  471. set shouldRefreshChildren(x) {
  472. this._shouldRefreshChildren = x;
  473. if (x && this.expanded)
  474. this.expand();
  475. },
  476. _setListItemNodeContent: function()
  477. {
  478. if (!this._listItemNode)
  479. return;
  480. if (typeof this._title === "string")
  481. this._listItemNode.textContent = this._title;
  482. else {
  483. this._listItemNode.removeChildren();
  484. if (this._title)
  485. this._listItemNode.appendChild(this._title);
  486. }
  487. }
  488. }
  489. TreeElement.prototype.appendChild = TreeOutline.prototype.appendChild;
  490. TreeElement.prototype.insertChild = TreeOutline.prototype.insertChild;
  491. TreeElement.prototype.insertBeforeChild = TreeOutline.prototype.insertBeforeChild;
  492. TreeElement.prototype.removeChild = TreeOutline.prototype.removeChild;
  493. TreeElement.prototype.removeChildAtIndex = TreeOutline.prototype.removeChildAtIndex;
  494. TreeElement.prototype.removeChildren = TreeOutline.prototype.removeChildren;
  495. TreeElement.prototype._attach = function()
  496. {
  497. if (!this._listItemNode || this.parent._shouldRefreshChildren) {
  498. if (this._listItemNode && this._listItemNode.parentNode)
  499. this._listItemNode.parentNode.removeChild(this._listItemNode);
  500. this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li");
  501. this._listItemNode.treeElement = this;
  502. this._setListItemNodeContent();
  503. this._listItemNode.title = this._tooltip ? this._tooltip : "";
  504. if (this.hidden)
  505. this._listItemNode.classList.add("hidden");
  506. if (this.hasChildren)
  507. this._listItemNode.classList.add("parent");
  508. if (this.expanded)
  509. this._listItemNode.classList.add("expanded");
  510. if (this.selected)
  511. this._listItemNode.classList.add("selected");
  512. this._listItemNode.addEventListener("mousedown", TreeElement.treeElementMouseDown, false);
  513. this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false);
  514. this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false);
  515. this.onattach();
  516. }
  517. var nextSibling = null;
  518. if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode)
  519. nextSibling = this.nextSibling._listItemNode;
  520. this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling);
  521. if (this._childrenListNode)
  522. this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
  523. if (this.selected)
  524. this.select();
  525. if (this.expanded)
  526. this.expand();
  527. }
  528. TreeElement.prototype._detach = function()
  529. {
  530. if (this._listItemNode && this._listItemNode.parentNode)
  531. this._listItemNode.parentNode.removeChild(this._listItemNode);
  532. if (this._childrenListNode && this._childrenListNode.parentNode)
  533. this._childrenListNode.parentNode.removeChild(this._childrenListNode);
  534. }
  535. TreeElement.treeElementMouseDown = function(event)
  536. {
  537. var element = event.currentTarget;
  538. if (!element || !element.treeElement || !element.treeElement.selectable)
  539. return;
  540. if (element.treeElement.isEventWithinDisclosureTriangle(event))
  541. return;
  542. element.treeElement.selectOnMouseDown(event);
  543. }
  544. TreeElement.treeElementToggled = function(event)
  545. {
  546. var element = event.currentTarget;
  547. if (!element || !element.treeElement)
  548. return;
  549. var toggleOnClick = element.treeElement.toggleOnClick && !element.treeElement.selectable;
  550. var isInTriangle = element.treeElement.isEventWithinDisclosureTriangle(event);
  551. if (!toggleOnClick && !isInTriangle)
  552. return;
  553. if (element.treeElement.expanded) {
  554. if (event.altKey)
  555. element.treeElement.collapseRecursively();
  556. else
  557. element.treeElement.collapse();
  558. } else {
  559. if (event.altKey)
  560. element.treeElement.expandRecursively();
  561. else
  562. element.treeElement.expand();
  563. }
  564. event.consume();
  565. }
  566. TreeElement.treeElementDoubleClicked = function(event)
  567. {
  568. var element = event.currentTarget;
  569. if (!element || !element.treeElement)
  570. return;
  571. var handled = element.treeElement.ondblclick.call(element.treeElement, event);
  572. if (handled)
  573. return;
  574. if (element.treeElement.hasChildren && !element.treeElement.expanded)
  575. element.treeElement.expand();
  576. }
  577. TreeElement.prototype.collapse = function()
  578. {
  579. if (this._listItemNode)
  580. this._listItemNode.classList.remove("expanded");
  581. if (this._childrenListNode)
  582. this._childrenListNode.classList.remove("expanded");
  583. this.expanded = false;
  584. if (this.treeOutline)
  585. this.treeOutline._expandedStateMap.put(this.representedObject, false);
  586. this.oncollapse();
  587. }
  588. TreeElement.prototype.collapseRecursively = function()
  589. {
  590. var item = this;
  591. while (item) {
  592. if (item.expanded)
  593. item.collapse();
  594. item = item.traverseNextTreeElement(false, this, true);
  595. }
  596. }
  597. TreeElement.prototype.expand = function()
  598. {
  599. if (!this.hasChildren || (this.expanded && !this._shouldRefreshChildren && this._childrenListNode))
  600. return;
  601. // Set this before onpopulate. Since onpopulate can add elements, this makes
  602. // sure the expanded flag is true before calling those functions. This prevents the possibility
  603. // of an infinite loop if onpopulate were to call expand.
  604. this.expanded = true;
  605. if (this.treeOutline)
  606. this.treeOutline._expandedStateMap.put(this.representedObject, true);
  607. if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) {
  608. if (this._childrenListNode && this._childrenListNode.parentNode)
  609. this._childrenListNode.parentNode.removeChild(this._childrenListNode);
  610. this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol");
  611. this._childrenListNode.parentTreeElement = this;
  612. this._childrenListNode.classList.add("children");
  613. if (this.hidden)
  614. this._childrenListNode.classList.add("hidden");
  615. this.onpopulate();
  616. for (var i = 0; i < this.children.length; ++i)
  617. this.children[i]._attach();
  618. delete this._shouldRefreshChildren;
  619. }
  620. if (this._listItemNode) {
  621. this._listItemNode.classList.add("expanded");
  622. if (this._childrenListNode && this._childrenListNode.parentNode != this._listItemNode.parentNode)
  623. this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
  624. }
  625. if (this._childrenListNode)
  626. this._childrenListNode.classList.add("expanded");
  627. this.onexpand();
  628. }
  629. TreeElement.prototype.expandRecursively = function(maxDepth)
  630. {
  631. var item = this;
  632. var info = {};
  633. var depth = 0;
  634. // The Inspector uses TreeOutlines to represents object properties, so recursive expansion
  635. // in some case can be infinite, since JavaScript objects can hold circular references.
  636. // So default to a recursion cap of 3 levels, since that gives fairly good results.
  637. if (isNaN(maxDepth))
  638. maxDepth = 3;
  639. while (item) {
  640. if (depth < maxDepth)
  641. item.expand();
  642. item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info);
  643. depth += info.depthChange;
  644. }
  645. }
  646. TreeElement.prototype.hasAncestor = function(ancestor) {
  647. if (!ancestor)
  648. return false;
  649. var currentNode = this.parent;
  650. while (currentNode) {
  651. if (ancestor === currentNode)
  652. return true;
  653. currentNode = currentNode.parent;
  654. }
  655. return false;
  656. }
  657. TreeElement.prototype.reveal = function()
  658. {
  659. var currentAncestor = this.parent;
  660. while (currentAncestor && !currentAncestor.root) {
  661. if (!currentAncestor.expanded)
  662. currentAncestor.expand();
  663. currentAncestor = currentAncestor.parent;
  664. }
  665. this.onreveal(this);
  666. }
  667. TreeElement.prototype.revealed = function()
  668. {
  669. var currentAncestor = this.parent;
  670. while (currentAncestor && !currentAncestor.root) {
  671. if (!currentAncestor.expanded)
  672. return false;
  673. currentAncestor = currentAncestor.parent;
  674. }
  675. return true;
  676. }
  677. TreeElement.prototype.selectOnMouseDown = function(event)
  678. {
  679. if (this.select(false, true))
  680. event.consume(true);
  681. }
  682. /**
  683. * @param {boolean=} omitFocus
  684. * @param {boolean=} selectedByUser
  685. * @return {boolean}
  686. */
  687. TreeElement.prototype.select = function(omitFocus, selectedByUser)
  688. {
  689. if (!this.treeOutline || !this.selectable || this.selected)
  690. return false;
  691. if (this.treeOutline.selectedTreeElement)
  692. this.treeOutline.selectedTreeElement.deselect();
  693. this.selected = true;
  694. if(!omitFocus)
  695. this.treeOutline._childrenListNode.focus();
  696. // Focusing on another node may detach "this" from tree.
  697. if (!this.treeOutline)
  698. return false;
  699. this.treeOutline.selectedTreeElement = this;
  700. if (this._listItemNode)
  701. this._listItemNode.classList.add("selected");
  702. return this.onselect(selectedByUser);
  703. }
  704. /**
  705. * @param {boolean=} omitFocus
  706. */
  707. TreeElement.prototype.revealAndSelect = function(omitFocus)
  708. {
  709. this.reveal();
  710. this.select(omitFocus);
  711. }
  712. /**
  713. * @param {boolean=} supressOnDeselect
  714. */
  715. TreeElement.prototype.deselect = function(supressOnDeselect)
  716. {
  717. if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected)
  718. return false;
  719. this.selected = false;
  720. this.treeOutline.selectedTreeElement = null;
  721. if (this._listItemNode)
  722. this._listItemNode.classList.remove("selected");
  723. return true;
  724. }
  725. // Overridden by subclasses.
  726. TreeElement.prototype.onpopulate = function() { }
  727. TreeElement.prototype.onenter = function() { }
  728. TreeElement.prototype.ondelete = function() { }
  729. TreeElement.prototype.onspace = function() { }
  730. TreeElement.prototype.onattach = function() { }
  731. TreeElement.prototype.onexpand = function() { }
  732. TreeElement.prototype.oncollapse = function() { }
  733. TreeElement.prototype.ondblclick = function() { }
  734. TreeElement.prototype.onreveal = function() { }
  735. /** @param {boolean=} selectedByUser */
  736. TreeElement.prototype.onselect = function(selectedByUser) { }
  737. /**
  738. * @param {boolean} skipUnrevealed
  739. * @param {(TreeOutline|TreeElement)=} stayWithin
  740. * @param {boolean=} dontPopulate
  741. * @param {Object=} info
  742. * @return {TreeElement}
  743. */
  744. TreeElement.prototype.traverseNextTreeElement = function(skipUnrevealed, stayWithin, dontPopulate, info)
  745. {
  746. if (!dontPopulate && this.hasChildren)
  747. this.onpopulate();
  748. if (info)
  749. info.depthChange = 0;
  750. var element = skipUnrevealed ? (this.revealed() ? this.children[0] : null) : this.children[0];
  751. if (element && (!skipUnrevealed || (skipUnrevealed && this.expanded))) {
  752. if (info)
  753. info.depthChange = 1;
  754. return element;
  755. }
  756. if (this === stayWithin)
  757. return null;
  758. element = skipUnrevealed ? (this.revealed() ? this.nextSibling : null) : this.nextSibling;
  759. if (element)
  760. return element;
  761. element = this;
  762. while (element && !element.root && !(skipUnrevealed ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) {
  763. if (info)
  764. info.depthChange -= 1;
  765. element = element.parent;
  766. }
  767. if (!element)
  768. return null;
  769. return (skipUnrevealed ? (element.revealed() ? element.nextSibling : null) : element.nextSibling);
  770. }
  771. /**
  772. * @param {boolean} skipUnrevealed
  773. * @param {boolean=} dontPopulate
  774. * @return {TreeElement}
  775. */
  776. TreeElement.prototype.traversePreviousTreeElement = function(skipUnrevealed, dontPopulate)
  777. {
  778. var element = skipUnrevealed ? (this.revealed() ? this.previousSibling : null) : this.previousSibling;
  779. if (!dontPopulate && element && element.hasChildren)
  780. element.onpopulate();
  781. while (element && (skipUnrevealed ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1])) {
  782. if (!dontPopulate && element.hasChildren)
  783. element.onpopulate();
  784. element = (skipUnrevealed ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1]);
  785. }
  786. if (element)
  787. return element;
  788. if (!this.parent || this.parent.root)
  789. return null;
  790. return this.parent;
  791. }
  792. TreeElement.prototype.isEventWithinDisclosureTriangle = function(event)
  793. {
  794. // FIXME: We should not use getComputedStyle(). For that we need to get rid of using ::before for disclosure triangle. (http://webk.it/74446)
  795. var paddingLeftValue = window.getComputedStyle(this._listItemNode).getPropertyCSSValue("padding-left");
  796. var computedLeftPadding = paddingLeftValue ? paddingLeftValue.getFloatValue(CSSPrimitiveValue.CSS_PX) : 0;
  797. var left = this._listItemNode.totalOffsetLeft() + computedLeftPadding;
  798. return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren;
  799. }