FastListWidget.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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 EventEmitter = require("devtools/shared/event-emitter");
  7. const { ViewHelpers } = require("devtools/client/shared/widgets/view-helpers");
  8. /**
  9. * A list menu widget that attempts to be very fast.
  10. *
  11. * Note: this widget should be used in tandem with the WidgetMethods in
  12. * view-helpers.js.
  13. *
  14. * @param nsIDOMNode aNode
  15. * The element associated with the widget.
  16. */
  17. const FastListWidget = module.exports = function FastListWidget(node) {
  18. this.document = node.ownerDocument;
  19. this.window = this.document.defaultView;
  20. this._parent = node;
  21. this._fragment = this.document.createDocumentFragment();
  22. // This is a prototype element that each item added to the list clones.
  23. this._templateElement = this.document.createElement("hbox");
  24. // Create an internal scrollbox container.
  25. this._list = this.document.createElement("scrollbox");
  26. this._list.className = "fast-list-widget-container theme-body";
  27. this._list.setAttribute("flex", "1");
  28. this._list.setAttribute("orient", "vertical");
  29. this._list.setAttribute("tabindex", "0");
  30. this._list.addEventListener("keypress", e => this.emit("keyPress", e), false);
  31. this._list.addEventListener("mousedown", e => this.emit("mousePress", e),
  32. false);
  33. this._parent.appendChild(this._list);
  34. this._orderedMenuElementsArray = [];
  35. this._itemsByElement = new Map();
  36. // This widget emits events that can be handled in a MenuContainer.
  37. EventEmitter.decorate(this);
  38. // Delegate some of the associated node's methods to satisfy the interface
  39. // required by MenuContainer instances.
  40. ViewHelpers.delegateWidgetAttributeMethods(this, node);
  41. ViewHelpers.delegateWidgetEventMethods(this, node);
  42. };
  43. FastListWidget.prototype = {
  44. /**
  45. * Inserts an item in this container at the specified index, optionally
  46. * grouping by name.
  47. *
  48. * @param number aIndex
  49. * The position in the container intended for this item.
  50. * @param nsIDOMNode aContents
  51. * The node to be displayed in the container.
  52. * @param Object aAttachment [optional]
  53. * Extra data for the user.
  54. * @return nsIDOMNode
  55. * The element associated with the displayed item.
  56. */
  57. insertItemAt: function (index, contents, attachment = {}) {
  58. let element = this._templateElement.cloneNode();
  59. element.appendChild(contents);
  60. if (index >= 0) {
  61. throw new Error("FastListWidget only supports appending items.");
  62. }
  63. this._fragment.appendChild(element);
  64. this._orderedMenuElementsArray.push(element);
  65. this._itemsByElement.set(element, this);
  66. return element;
  67. },
  68. /**
  69. * This is a non-standard widget implementation method. When appending items,
  70. * they are queued in a document fragment. This method appends the document
  71. * fragment to the dom.
  72. */
  73. flush: function () {
  74. this._list.appendChild(this._fragment);
  75. },
  76. /**
  77. * Removes all of the child nodes from this container.
  78. */
  79. removeAllItems: function () {
  80. let list = this._list;
  81. while (list.hasChildNodes()) {
  82. list.firstChild.remove();
  83. }
  84. this._selectedItem = null;
  85. this._orderedMenuElementsArray.length = 0;
  86. this._itemsByElement.clear();
  87. },
  88. /**
  89. * Remove the given item.
  90. */
  91. removeChild: function (child) {
  92. throw new Error("Not yet implemented");
  93. },
  94. /**
  95. * Gets the currently selected child node in this container.
  96. * @return nsIDOMNode
  97. */
  98. get selectedItem() {
  99. return this._selectedItem;
  100. },
  101. /**
  102. * Sets the currently selected child node in this container.
  103. * @param nsIDOMNode child
  104. */
  105. set selectedItem(child) {
  106. let menuArray = this._orderedMenuElementsArray;
  107. if (!child) {
  108. this._selectedItem = null;
  109. }
  110. for (let node of menuArray) {
  111. if (node == child) {
  112. node.classList.add("selected");
  113. this._selectedItem = node;
  114. } else {
  115. node.classList.remove("selected");
  116. }
  117. }
  118. this.ensureElementIsVisible(this.selectedItem);
  119. },
  120. /**
  121. * Returns the child node in this container situated at the specified index.
  122. *
  123. * @param number index
  124. * The position in the container intended for this item.
  125. * @return nsIDOMNode
  126. * The element associated with the displayed item.
  127. */
  128. getItemAtIndex: function (index) {
  129. return this._orderedMenuElementsArray[index];
  130. },
  131. /**
  132. * Adds a new attribute or changes an existing attribute on this container.
  133. *
  134. * @param string name
  135. * The name of the attribute.
  136. * @param string value
  137. * The desired attribute value.
  138. */
  139. setAttribute: function (name, value) {
  140. this._parent.setAttribute(name, value);
  141. if (name == "emptyText") {
  142. this._textWhenEmpty = value;
  143. }
  144. },
  145. /**
  146. * Removes an attribute on this container.
  147. *
  148. * @param string name
  149. * The name of the attribute.
  150. */
  151. removeAttribute: function (name) {
  152. this._parent.removeAttribute(name);
  153. if (name == "emptyText") {
  154. this._removeEmptyText();
  155. }
  156. },
  157. /**
  158. * Ensures the specified element is visible.
  159. *
  160. * @param nsIDOMNode element
  161. * The element to make visible.
  162. */
  163. ensureElementIsVisible: function (element) {
  164. if (!element) {
  165. return;
  166. }
  167. // Ensure the element is visible but not scrolled horizontally.
  168. let boxObject = this._list.boxObject;
  169. boxObject.ensureElementIsVisible(element);
  170. boxObject.scrollBy(-this._list.clientWidth, 0);
  171. },
  172. /**
  173. * Sets the text displayed in this container when empty.
  174. * @param string aValue
  175. */
  176. set _textWhenEmpty(value) {
  177. if (this._emptyTextNode) {
  178. this._emptyTextNode.setAttribute("value", value);
  179. }
  180. this._emptyTextValue = value;
  181. this._showEmptyText();
  182. },
  183. /**
  184. * Creates and appends a label signaling that this container is empty.
  185. */
  186. _showEmptyText: function () {
  187. if (this._emptyTextNode || !this._emptyTextValue) {
  188. return;
  189. }
  190. let label = this.document.createElement("label");
  191. label.className = "plain fast-list-widget-empty-text";
  192. label.setAttribute("value", this._emptyTextValue);
  193. this._parent.insertBefore(label, this._list);
  194. this._emptyTextNode = label;
  195. },
  196. /**
  197. * Removes the label signaling that this container is empty.
  198. */
  199. _removeEmptyText: function () {
  200. if (!this._emptyTextNode) {
  201. return;
  202. }
  203. this._parent.removeChild(this._emptyTextNode);
  204. this._emptyTextNode = null;
  205. },
  206. window: null,
  207. document: null,
  208. _parent: null,
  209. _list: null,
  210. _selectedItem: null,
  211. _orderedMenuElementsArray: null,
  212. _itemsByElement: null,
  213. _emptyTextNode: null,
  214. _emptyTextValue: ""
  215. };