devtools/client/shared/widgets/SimpleListWidget.jsm
author Bogdan Tara <btara@mozilla.com>
Thu, 12 Jul 2018 01:50:53 +0300
changeset 481348 d610dd7bfbcdc0ec00ce393dff833f2d8e1b7986
parent 481347 b099e7e0b26494e0b2cdc549f75ff19291627968
child 481513 3af5036936c12d911a0a3de1ef86f692e7878486
permissions -rw-r--r--
Backed out changeset b099e7e0b264 (bug 1454358) for build bustages on Element.h CLOSED TREE

/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
const { ViewHelpers } = require("devtools/client/shared/widgets/view-helpers");

this.EXPORTED_SYMBOLS = ["SimpleListWidget"];

/**
 * A very simple vertical list view.
 *
 * Note: this widget should be used in tandem with the WidgetMethods in
 * view-helpers.js.
 *
 * @param Node aNode
 *        The element associated with the widget.
 */
function SimpleListWidget(aNode) {
  this.document = aNode.ownerDocument;
  this.window = this.document.defaultView;
  this._parent = aNode;

  // Create an internal list container.
  this._list = this.document.createElement("scrollbox");
  this._list.className = "simple-list-widget-container theme-body";
  this._list.setAttribute("flex", "1");
  this._list.setAttribute("orient", "vertical");
  this._parent.appendChild(this._list);

  // Delegate some of the associated node's methods to satisfy the interface
  // required by WidgetMethods instances.
  ViewHelpers.delegateWidgetAttributeMethods(this, aNode);
  ViewHelpers.delegateWidgetEventMethods(this, aNode);
}
this.SimpleListWidget = SimpleListWidget;

SimpleListWidget.prototype = {
  /**
   * Inserts an item in this container at the specified index.
   *
   * @param number aIndex
   *        The position in the container intended for this item.
   * @param Node aContents
   *        The node displayed in the container.
   * @return Node
   *         The element associated with the displayed item.
   */
  insertItemAt: function(aIndex, aContents) {
    aContents.classList.add("simple-list-widget-item");

    const list = this._list;
    return list.insertBefore(aContents, list.childNodes[aIndex]);
  },

  /**
   * Returns the child node in this container situated at the specified index.
   *
   * @param number aIndex
   *        The position in the container intended for this item.
   * @return Node
   *         The element associated with the displayed item.
   */
  getItemAtIndex: function(aIndex) {
    return this._list.childNodes[aIndex];
  },

  /**
   * Immediately removes the specified child node from this container.
   *
   * @param Node aChild
   *        The element associated with the displayed item.
   */
  removeChild: function(aChild) {
    this._list.removeChild(aChild);

    if (this._selectedItem == aChild) {
      this._selectedItem = null;
    }
  },

  /**
   * Removes all of the child nodes from this container.
   */
  removeAllItems: function() {
    const list = this._list;
    const parent = this._parent;

    while (list.hasChildNodes()) {
      list.firstChild.remove();
    }

    parent.scrollTop = 0;
    parent.scrollLeft = 0;
    this._selectedItem = null;
  },

  /**
   * Gets the currently selected child node in this container.
   * @return Node
   */
  get selectedItem() {
    return this._selectedItem;
  },

  /**
   * Sets the currently selected child node in this container.
   * @param Node aChild
   */
  set selectedItem(aChild) {
    const childNodes = this._list.childNodes;

    if (!aChild) {
      this._selectedItem = null;
    }
    for (const node of childNodes) {
      if (node == aChild) {
        node.classList.add("selected");
        this._selectedItem = node;
      } else {
        node.classList.remove("selected");
      }
    }
  },

  /**
   * Adds a new attribute or changes an existing attribute on this container.
   *
   * @param string aName
   *        The name of the attribute.
   * @param string aValue
   *        The desired attribute value.
   */
  setAttribute: function(aName, aValue) {
    this._parent.setAttribute(aName, aValue);

    if (aName == "emptyText") {
      this._textWhenEmpty = aValue;
    } else if (aName == "headerText") {
      this._textAsHeader = aValue;
    }
  },

  /**
   * Removes an attribute on this container.
   *
   * @param string aName
   *        The name of the attribute.
   */
  removeAttribute: function(aName) {
    this._parent.removeAttribute(aName);

    if (aName == "emptyText") {
      this._removeEmptyText();
    }
  },

  /**
   * Ensures the specified element is visible.
   *
   * @param Node aElement
   *        The element to make visible.
   */
  ensureElementIsVisible: function(aElement) {
    if (!aElement) {
      return;
    }

    // Ensure the element is visible but not scrolled horizontally.
    const boxObject = this._list.boxObject;
    boxObject.ensureElementIsVisible(aElement);
    boxObject.scrollBy(-this._list.clientWidth, 0);
  },

  /**
   * Sets the text displayed permanently in this container as a header.
   * @param string aValue
   */
  set _textAsHeader(aValue) {
    if (this._headerTextNode) {
      this._headerTextNode.setAttribute("value", aValue);
    }
    this._headerTextValue = aValue;
    this._showHeaderText();
  },

  /**
   * Sets the text displayed in this container when empty.
   * @param string aValue
   */
  set _textWhenEmpty(aValue) {
    if (this._emptyTextNode) {
      this._emptyTextNode.setAttribute("value", aValue);
    }
    this._emptyTextValue = aValue;
    this._showEmptyText();
  },

  /**
   * Creates and appends a label displayed as this container's header.
   */
  _showHeaderText: function() {
    if (this._headerTextNode || !this._headerTextValue) {
      return;
    }
    const label = this.document.createElement("label");
    label.className = "plain simple-list-widget-perma-text";
    label.setAttribute("value", this._headerTextValue);

    this._parent.insertBefore(label, this._list);
    this._headerTextNode = label;
  },

  /**
   * Creates and appends a label signaling that this container is empty.
   */
  _showEmptyText: function() {
    if (this._emptyTextNode || !this._emptyTextValue) {
      return;
    }
    const label = this.document.createElement("label");
    label.className = "plain simple-list-widget-empty-text";
    label.setAttribute("value", this._emptyTextValue);

    this._parent.appendChild(label);
    this._emptyTextNode = label;
  },

  /**
   * Removes the label signaling that this container is empty.
   */
  _removeEmptyText: function() {
    if (!this._emptyTextNode) {
      return;
    }
    this._parent.removeChild(this._emptyTextNode);
    this._emptyTextNode = null;
  },

  window: null,
  document: null,
  _parent: null,
  _list: null,
  _selectedItem: null,
  _headerTextNode: null,
  _headerTextValue: "",
  _emptyTextNode: null,
  _emptyTextValue: ""
};