browser/devtools/shared/widgets/FastListWidget.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 "use strict";
     8 const EventEmitter = require("devtools/toolkit/event-emitter");
     9 const { Cu, Ci } = require("chrome");
    10 const { ViewHelpers } = Cu.import("resource:///modules/devtools/ViewHelpers.jsm", {});
    12 /**
    13  * A list menu widget that attempts to be very fast.
    14  *
    15  * Note: this widget should be used in tandem with the WidgetMethods in
    16  * ViewHelpers.jsm.
    17  *
    18  * @param nsIDOMNode aNode
    19  *        The element associated with the widget.
    20  */
    21 const FastListWidget = module.exports = function FastListWidget(aNode) {
    22   this.document = aNode.ownerDocument;
    23   this.window = this.document.defaultView;
    24   this._parent = aNode;
    25   this._fragment = this.document.createDocumentFragment();
    27   // This is a prototype element that each item added to the list clones.
    28   this._templateElement = this.document.createElement("hbox");
    30   // Create an internal scrollbox container.
    31   this._list = this.document.createElement("scrollbox");
    32   this._list.className = "fast-list-widget-container theme-body";
    33   this._list.setAttribute("flex", "1");
    34   this._list.setAttribute("orient", "vertical");
    35   this._list.setAttribute("tabindex", "0");
    36   this._list.addEventListener("keypress", e => this.emit("keyPress", e), false);
    37   this._list.addEventListener("mousedown", e => this.emit("mousePress", e), false);
    38   this._parent.appendChild(this._list);
    40   this._orderedMenuElementsArray = [];
    41   this._itemsByElement = new Map();
    43   // This widget emits events that can be handled in a MenuContainer.
    44   EventEmitter.decorate(this);
    46   // Delegate some of the associated node's methods to satisfy the interface
    47   // required by MenuContainer instances.
    48   ViewHelpers.delegateWidgetAttributeMethods(this, aNode);
    49   ViewHelpers.delegateWidgetEventMethods(this, aNode);
    50 }
    52 FastListWidget.prototype = {
    53   /**
    54    * Inserts an item in this container at the specified index, optionally
    55    * grouping by name.
    56    *
    57    * @param number aIndex
    58    *        The position in the container intended for this item.
    59    * @param nsIDOMNode aContents
    60    *        The node to be displayed in the container.
    61    * @param Object aAttachment [optional]
    62    *        Extra data for the user.
    63    * @return nsIDOMNode
    64    *         The element associated with the displayed item.
    65    */
    66   insertItemAt: function(aIndex, aContents, aAttachment={}) {
    67     let element = this._templateElement.cloneNode();
    68     element.appendChild(aContents);
    70     if (aIndex >= 0) {
    71       throw new Error("FastListWidget only supports appending items.");
    72     }
    74     this._fragment.appendChild(element);
    75     this._orderedMenuElementsArray.push(element);
    76     this._itemsByElement.set(element, this);
    78     return element;
    79   },
    81   /**
    82    * This is a non-standard widget implementation method. When appending items,
    83    * they are queued in a document fragment. This method appends the document
    84    * fragment to the dom.
    85    */
    86   flush: function() {
    87     this._list.appendChild(this._fragment);
    88   },
    90   /**
    91    * Removes all of the child nodes from this container.
    92    */
    93   removeAllItems: function() {
    94     let parent = this._parent;
    95     let list = this._list;
    97     while (list.hasChildNodes()) {
    98       list.firstChild.remove();
    99     }
   101     this._selectedItem = null;
   103     this._orderedMenuElementsArray.length = 0;
   104     this._itemsByElement.clear();
   105   },
   107   /**
   108    * Remove the given item.
   109    */
   110   removeChild: function(child) {
   111     throw new Error("Not yet implemented");
   112   },
   114   /**
   115    * Gets the currently selected child node in this container.
   116    * @return nsIDOMNode
   117    */
   118   get selectedItem() {
   119     return this._selectedItem;
   120   },
   122   /**
   123    * Sets the currently selected child node in this container.
   124    * @param nsIDOMNode child
   125    */
   126   set selectedItem(child) {
   127     let menuArray = this._orderedMenuElementsArray;
   129     if (!child) {
   130       this._selectedItem = null;
   131     }
   132     for (let node of menuArray) {
   133       if (node == child) {
   134         node.classList.add("selected");
   135         this._selectedItem = node;
   136       } else {
   137         node.classList.remove("selected");
   138       }
   139     }
   141     this.ensureElementIsVisible(this.selectedItem);
   142   },
   144   /**
   145    * Returns the child node in this container situated at the specified index.
   146    *
   147    * @param number index
   148    *        The position in the container intended for this item.
   149    * @return nsIDOMNode
   150    *         The element associated with the displayed item.
   151    */
   152   getItemAtIndex: function(index) {
   153     return this._orderedMenuElementsArray[index];
   154   },
   156   /**
   157    * Adds a new attribute or changes an existing attribute on this container.
   158    *
   159    * @param string name
   160    *        The name of the attribute.
   161    * @param string value
   162    *        The desired attribute value.
   163    */
   164   setAttribute: function(name, value) {
   165     this._parent.setAttribute(name, value);
   167     if (name == "emptyText") {
   168       this._textWhenEmpty = value;
   169     }
   170   },
   172   /**
   173    * Removes an attribute on this container.
   174    *
   175    * @param string name
   176    *        The name of the attribute.
   177    */
   178   removeAttribute: function(name) {
   179     this._parent.removeAttribute(name);
   181     if (name == "emptyText") {
   182       this._removeEmptyText();
   183     }
   184   },
   186   /**
   187    * Ensures the specified element is visible.
   188    *
   189    * @param nsIDOMNode element
   190    *        The element to make visible.
   191    */
   192   ensureElementIsVisible: function(element) {
   193     if (!element) {
   194       return;
   195     }
   197     // Ensure the element is visible but not scrolled horizontally.
   198     let boxObject = this._list.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
   199     boxObject.ensureElementIsVisible(element);
   200     boxObject.scrollBy(-this._list.clientWidth, 0);
   201   },
   203   /**
   204    * Sets the text displayed in this container when empty.
   205    * @param string aValue
   206    */
   207   set _textWhenEmpty(aValue) {
   208     if (this._emptyTextNode) {
   209       this._emptyTextNode.setAttribute("value", aValue);
   210     }
   211     this._emptyTextValue = aValue;
   212     this._showEmptyText();
   213   },
   215   /**
   216    * Creates and appends a label signaling that this container is empty.
   217    */
   218   _showEmptyText: function() {
   219     if (this._emptyTextNode || !this._emptyTextValue) {
   220       return;
   221     }
   222     let label = this.document.createElement("label");
   223     label.className = "plain fast-list-widget-empty-text";
   224     label.setAttribute("value", this._emptyTextValue);
   226     this._parent.insertBefore(label, this._list);
   227     this._emptyTextNode = label;
   228   },
   230   /**
   231    * Removes the label signaling that this container is empty.
   232    */
   233   _removeEmptyText: function() {
   234     if (!this._emptyTextNode) {
   235       return;
   236     }
   237     this._parent.removeChild(this._emptyTextNode);
   238     this._emptyTextNode = null;
   239   },
   241   window: null,
   242   document: null,
   243   _parent: null,
   244   _list: null,
   245   _selectedItem: null,
   246   _orderedMenuElementsArray: null,
   247   _itemsByElement: null,
   248   _emptyTextNode: null,
   249   _emptyTextValue: ""
   250 };

mercurial