BreadcrumbsWidget.jsm 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. "use strict";
  6. const Cu = Components.utils;
  7. const ENSURE_SELECTION_VISIBLE_DELAY = 50; // ms
  8. const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
  9. const { ViewHelpers, setNamedTimeout } = require("devtools/client/shared/widgets/view-helpers");
  10. const EventEmitter = require("devtools/shared/event-emitter");
  11. this.EXPORTED_SYMBOLS = ["BreadcrumbsWidget"];
  12. /**
  13. * A breadcrumb-like list of items.
  14. *
  15. * Note: this widget should be used in tandem with the WidgetMethods in
  16. * view-helpers.js.
  17. *
  18. * @param nsIDOMNode aNode
  19. * The element associated with the widget.
  20. * @param Object aOptions
  21. * - smoothScroll: specifies if smooth scrolling on selection is enabled.
  22. */
  23. this.BreadcrumbsWidget = function BreadcrumbsWidget(aNode, aOptions = {}) {
  24. this.document = aNode.ownerDocument;
  25. this.window = this.document.defaultView;
  26. this._parent = aNode;
  27. // Create an internal arrowscrollbox container.
  28. this._list = this.document.createElement("arrowscrollbox");
  29. this._list.className = "breadcrumbs-widget-container";
  30. this._list.setAttribute("flex", "1");
  31. this._list.setAttribute("orient", "horizontal");
  32. this._list.setAttribute("clicktoscroll", "true");
  33. this._list.setAttribute("smoothscroll", !!aOptions.smoothScroll);
  34. this._list.addEventListener("keypress", e => this.emit("keyPress", e), false);
  35. this._list.addEventListener("mousedown", e => this.emit("mousePress", e), false);
  36. this._parent.appendChild(this._list);
  37. // By default, hide the arrows. We let the arrowscrollbox show them
  38. // in case of overflow.
  39. this._list._scrollButtonUp.collapsed = true;
  40. this._list._scrollButtonDown.collapsed = true;
  41. this._list.addEventListener("underflow", this._onUnderflow.bind(this), false);
  42. this._list.addEventListener("overflow", this._onOverflow.bind(this), false);
  43. // This widget emits events that can be handled in a MenuContainer.
  44. EventEmitter.decorate(this);
  45. // Delegate some of the associated node's methods to satisfy the interface
  46. // required by MenuContainer instances.
  47. ViewHelpers.delegateWidgetAttributeMethods(this, aNode);
  48. ViewHelpers.delegateWidgetEventMethods(this, aNode);
  49. };
  50. BreadcrumbsWidget.prototype = {
  51. /**
  52. * Inserts an item in this container at the specified index.
  53. *
  54. * @param number aIndex
  55. * The position in the container intended for this item.
  56. * @param nsIDOMNode aContents
  57. * The node displayed in the container.
  58. * @return nsIDOMNode
  59. * The element associated with the displayed item.
  60. */
  61. insertItemAt: function (aIndex, aContents) {
  62. let list = this._list;
  63. let breadcrumb = new Breadcrumb(this, aContents);
  64. return list.insertBefore(breadcrumb._target, list.childNodes[aIndex]);
  65. },
  66. /**
  67. * Returns the child node in this container situated at the specified index.
  68. *
  69. * @param number aIndex
  70. * The position in the container intended for this item.
  71. * @return nsIDOMNode
  72. * The element associated with the displayed item.
  73. */
  74. getItemAtIndex: function (aIndex) {
  75. return this._list.childNodes[aIndex];
  76. },
  77. /**
  78. * Removes the specified child node from this container.
  79. *
  80. * @param nsIDOMNode aChild
  81. * The element associated with the displayed item.
  82. */
  83. removeChild: function (aChild) {
  84. this._list.removeChild(aChild);
  85. if (this._selectedItem == aChild) {
  86. this._selectedItem = null;
  87. }
  88. },
  89. /**
  90. * Removes all of the child nodes from this container.
  91. */
  92. removeAllItems: function () {
  93. let list = this._list;
  94. while (list.hasChildNodes()) {
  95. list.firstChild.remove();
  96. }
  97. this._selectedItem = null;
  98. },
  99. /**
  100. * Gets the currently selected child node in this container.
  101. * @return nsIDOMNode
  102. */
  103. get selectedItem() {
  104. return this._selectedItem;
  105. },
  106. /**
  107. * Sets the currently selected child node in this container.
  108. * @param nsIDOMNode aChild
  109. */
  110. set selectedItem(aChild) {
  111. let childNodes = this._list.childNodes;
  112. if (!aChild) {
  113. this._selectedItem = null;
  114. }
  115. for (let node of childNodes) {
  116. if (node == aChild) {
  117. node.setAttribute("checked", "");
  118. this._selectedItem = node;
  119. } else {
  120. node.removeAttribute("checked");
  121. }
  122. }
  123. },
  124. /**
  125. * Returns the value of the named attribute on this container.
  126. *
  127. * @param string aName
  128. * The name of the attribute.
  129. * @return string
  130. * The current attribute value.
  131. */
  132. getAttribute: function (aName) {
  133. if (aName == "scrollPosition") return this._list.scrollPosition;
  134. if (aName == "scrollWidth") return this._list.scrollWidth;
  135. return this._parent.getAttribute(aName);
  136. },
  137. /**
  138. * Ensures the specified element is visible.
  139. *
  140. * @param nsIDOMNode aElement
  141. * The element to make visible.
  142. */
  143. ensureElementIsVisible: function (aElement) {
  144. if (!aElement) {
  145. return;
  146. }
  147. // Repeated calls to ensureElementIsVisible would interfere with each other
  148. // and may sometimes result in incorrect scroll positions.
  149. setNamedTimeout("breadcrumb-select", ENSURE_SELECTION_VISIBLE_DELAY, () => {
  150. if (this._list.ensureElementIsVisible) {
  151. this._list.ensureElementIsVisible(aElement);
  152. }
  153. });
  154. },
  155. /**
  156. * The underflow and overflow listener for the arrowscrollbox container.
  157. */
  158. _onUnderflow: function ({ target }) {
  159. if (target != this._list) {
  160. return;
  161. }
  162. target._scrollButtonUp.collapsed = true;
  163. target._scrollButtonDown.collapsed = true;
  164. target.removeAttribute("overflows");
  165. },
  166. /**
  167. * The underflow and overflow listener for the arrowscrollbox container.
  168. */
  169. _onOverflow: function ({ target }) {
  170. if (target != this._list) {
  171. return;
  172. }
  173. target._scrollButtonUp.collapsed = false;
  174. target._scrollButtonDown.collapsed = false;
  175. target.setAttribute("overflows", "");
  176. },
  177. window: null,
  178. document: null,
  179. _parent: null,
  180. _list: null,
  181. _selectedItem: null
  182. };
  183. /**
  184. * A Breadcrumb constructor for the BreadcrumbsWidget.
  185. *
  186. * @param BreadcrumbsWidget aWidget
  187. * The widget to contain this breadcrumb.
  188. * @param nsIDOMNode aContents
  189. * The node displayed in the container.
  190. */
  191. function Breadcrumb(aWidget, aContents) {
  192. this.document = aWidget.document;
  193. this.window = aWidget.window;
  194. this.ownerView = aWidget;
  195. this._target = this.document.createElement("hbox");
  196. this._target.className = "breadcrumbs-widget-item";
  197. this._target.setAttribute("align", "center");
  198. this.contents = aContents;
  199. }
  200. Breadcrumb.prototype = {
  201. /**
  202. * Sets the contents displayed in this item's view.
  203. *
  204. * @param string | nsIDOMNode aContents
  205. * The string or node displayed in the container.
  206. */
  207. set contents(aContents) {
  208. // If there are already some contents displayed, replace them.
  209. if (this._target.hasChildNodes()) {
  210. this._target.replaceChild(aContents, this._target.firstChild);
  211. return;
  212. }
  213. // These are the first contents ever displayed.
  214. this._target.appendChild(aContents);
  215. },
  216. window: null,
  217. document: null,
  218. ownerView: null,
  219. _target: null
  220. };