Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/devtools/client/shared/widgets/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 17 kB image not shown  

Quelle  TreeWidget.js   Sprache: JAVA

 
/* 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 HTML_NS = "http://www.w3.org/1999/xhtml";

const EventEmitter = require("resource://devtools/shared/event-emitter.js");
const { KeyCodes } = require("resource://devtools/client/shared/keycodes.js");

/**
 * A tree widget with keyboard navigation and collapsable structure.
 *
 * @param {Node} node
 *        The container element for the tree widget.
 * @param {Object} options
 *        - emptyText {string}: text to display when no entries in the table.
 *        - defaultType {string}: The default type of the tree items. For ex.
 *          'js'
 *        - sorted {boolean}: Defaults to true. If true, tree items are kept in
 *          lexical order. If false, items will be kept in insertion order.
 *        - contextMenuId {string}: ID of context menu to be displayed on
 *          tree items.
 */

function TreeWidget(node, options = {}) {
  EventEmitter.decorate(this);

  this.document = node.ownerDocument;
  this.window = this.document.defaultView;
  this._parent = node;

  this.emptyText = options.emptyText || "";
  this.defaultType = options.defaultType;
  this.sorted = options.sorted !== false;
  this.contextMenuId = options.contextMenuId;

  this.setupRoot();

  this.placeholder = this.document.createElementNS(HTML_NS, "label");
  this.placeholder.className = "tree-widget-empty-text";
  this._parent.appendChild(this.placeholder);

  if (this.emptyText) {
    this.setPlaceholderText(this.emptyText);
  }
  // A map to hold all the passed attachment to each leaf in the tree.
  this.attachments = new Map();
}

TreeWidget.prototype = {
  _selectedLabel: null,
  _selectedItem: null,

  /**
   * Select any node in the tree.
   *
   * @param {array} ids
   *        An array of ids leading upto the selected item
   */

  set selectedItem(ids) {
    if (this._selectedLabel) {
      this._selectedLabel.classList.remove("theme-selected");
    }
    const currentSelected = this._selectedLabel;
    if (ids == -1) {
      this._selectedLabel = this._selectedItem = null;
      return;
    }
    if (!Array.isArray(ids)) {
      return;
    }
    this._selectedLabel = this.root.setSelectedItem(ids);
    if (!this._selectedLabel) {
      this._selectedItem = null;
    } else {
      if (currentSelected != this._selectedLabel) {
        this.ensureSelectedVisible();
      }
      this._selectedItem = ids;
      this.emit(
        "select",
        this._selectedItem,
        this.attachments.get(JSON.stringify(ids))
      );
    }
  },

  /**
   * Gets the selected item in the tree.
   *
   * @return {array}
   *        An array of ids leading upto the selected item
   */

  get selectedItem() {
    return this._selectedItem;
  },

  /**
   * Returns if the passed array corresponds to the selected item in the tree.
   *
   * @return {array}
   *        An array of ids leading upto the requested item
   */

  isSelected(item) {
    if (!this._selectedItem || this._selectedItem.length != item.length) {
      return false;
    }

    for (let i = 0; i < this._selectedItem.length; i++) {
      if (this._selectedItem[i] != item[i]) {
        return false;
      }
    }

    return true;
  },

  destroy() {
    this.root.remove();
    this.root = null;
  },

  /**
   * Sets up the root container of the TreeWidget.
   */

  setupRoot() {
    this.root = new TreeItem(this.document);
    if (this.contextMenuId) {
      this.root.children.addEventListener("contextmenu", event => {
        // Call stopPropagation() and preventDefault() here so that avoid to show default
        // context menu in about:devtools-toolbox. See Bug 1515265.
        event.stopPropagation();
        event.preventDefault();
        const menu = this.document.getElementById(this.contextMenuId);
        menu.openPopupAtScreen(event.screenX, event.screenY, true);
      });
    }

    this._parent.appendChild(this.root.children);

    this.root.children.addEventListener("mousedown", e => this.onClick(e));
    this.root.children.addEventListener("keydown", e => this.onKeydown(e));
  },

  /**
   * Sets the text to be shown when no node is present in the tree.
   * The placeholder will be hidden if text is empty.
   */

  setPlaceholderText(text) {
    this.placeholder.textContent = text;
    if (text) {
      this.placeholder.removeAttribute("hidden");
    } else {
      this.placeholder.setAttribute("hidden""true");
    }
  },

  /**
   * Select any node in the tree.
   *
   * @param {array} id
   *        An array of ids leading upto the selected item
   */

  selectItem(id) {
    this.selectedItem = id;
  },

  /**
   * Selects the next visible item in the tree.
   */

  selectNextItem() {
    const next = this.getNextVisibleItem();
    if (next) {
      this.selectedItem = next;
    }
  },

  /**
   * Selects the previos visible item in the tree
   */

  selectPreviousItem() {
    const prev = this.getPreviousVisibleItem();
    if (prev) {
      this.selectedItem = prev;
    }
  },

  /**
   * Returns the next visible item in the tree
   */

  getNextVisibleItem() {
    let node = this._selectedLabel;
    if (node.hasAttribute("expanded") && node.nextSibling.firstChild) {
      return JSON.parse(node.nextSibling.firstChild.getAttribute("data-id"));
    }
    node = node.parentNode;
    if (node.nextSibling) {
      return JSON.parse(node.nextSibling.getAttribute("data-id"));
    }
    node = node.parentNode;
    while (node.parentNode && node != this.root.children) {
      if (node.parentNode?.nextSibling) {
        return JSON.parse(node.parentNode.nextSibling.getAttribute("data-id"));
      }
      node = node.parentNode;
    }
    return null;
  },

  /**
   * Returns the previous visible item in the tree
   */

  getPreviousVisibleItem() {
    let node = this._selectedLabel.parentNode;
    if (node.previousSibling) {
      node = node.previousSibling.firstChild;
      while (node.hasAttribute("expanded") && !node.hasAttribute("empty")) {
        if (!node.nextSibling.lastChild) {
          break;
        }
        node = node.nextSibling.lastChild.firstChild;
      }
      return JSON.parse(node.parentNode.getAttribute("data-id"));
    }
    node = node.parentNode;
    if (node.parentNode && node != this.root.children) {
      node = node.parentNode;
      while (node.hasAttribute("expanded") && !node.hasAttribute("empty")) {
        if (!node.nextSibling.firstChild) {
          break;
        }
        node = node.nextSibling.firstChild.firstChild;
      }
      return JSON.parse(node.getAttribute("data-id"));
    }
    return null;
  },

  clearSelection() {
    this.selectedItem = -1;
  },

  /**
   * Adds an item in the tree. The item can be added as a child to any node in
   * the tree. The method will also create any subnode not present in the
   * process.
   *
   * @param {[string|object]} items
   *        An array of either string or objects where each increasing index
   *        represents an item corresponding to an equivalent depth in the tree.
   *        Each array element can be either just a string with the value as the
   *        id of of that item as well as the display value, or it can be an
   *        object with the following propeties:
   *          - id {string} The id of the item
   *          - label {string} The display value of the item
   *          - node {DOMNode} The dom node if you want to insert some custom
   *                 element as the item. The label property is not used in this
   *                 case
   *          - attachment {object} Any object to be associated with this item.
   *          - type {string} The type of this particular item. If this is null,
   *                 then defaultType will be used.
   *        For example, if items = ["foo", "bar", { id: "id1", label: "baz" }]
   *        and the tree is empty, then the following hierarchy will be created
   *        in the tree:
   *        foo
   *         └ bar
   *            └ baz
   *        Passing the string id instead of the complete object helps when you
   *        are simply adding children to an already existing node and you know
   *        its id.
   */

  add(items) {
    this.root.add(items, this.defaultType, this.sorted);
    for (let i = 0; i < items.length; i++) {
      if (items[i].attachment) {
        this.attachments.set(
          JSON.stringify(items.slice(0, i + 1).map(item => item.id || item)),
          items[i].attachment
        );
      }
    }
    // Empty the empty-tree-text
    this.setPlaceholderText("");
  },

  /**
   * Check if an item exists.
   *
   * @param {array} item
   *        The array of ids leading up to the item.
   */

  exists(item) {
    let bookmark = this.root;

    for (const id of item) {
      if (bookmark.items.has(id)) {
        bookmark = bookmark.items.get(id);
      } else {
        return false;
      }
    }
    return true;
  },

  /**
   * Removes the specified item and all of its child items from the tree.
   *
   * @param {array} item
   *        The array of ids leading up to the item.
   */

  remove(item) {
    this.root.remove(item);
    this.attachments.delete(JSON.stringify(item));
    // Display the empty tree text
    if (this.root.items.size == 0 && this.emptyText) {
      this.setPlaceholderText(this.emptyText);
    }
  },

  /**
   * Removes all of the child nodes from this tree.
   */

  clear() {
    this.root.remove();
    this.setupRoot();
    this.attachments.clear();
    if (this.emptyText) {
      this.setPlaceholderText(this.emptyText);
    }
  },

  /**
   * Expands the tree completely
   */

  expandAll() {
    this.root.expandAll();
  },

  /**
   * Collapses the tree completely
   */

  collapseAll() {
    this.root.collapseAll();
  },

  /**
   * Click handler for the tree. Used to select, open and close the tree nodes.
   */

  onClick(event) {
    let target = event.originalTarget;
    while (target && !target.classList.contains("tree-widget-item")) {
      if (target == this.root.children) {
        return;
      }
      target = target.parentNode;
    }
    if (!target) {
      return;
    }

    if (target.hasAttribute("expanded")) {
      target.removeAttribute("expanded");
    } else {
      target.setAttribute("expanded""true");
    }

    if (this._selectedLabel != target) {
      const ids = target.parentNode.getAttribute("data-id");
      this.selectedItem = JSON.parse(ids);
    }
  },

  /**
   * Keydown handler for this tree. Used to select next and previous visible
   * items, as well as collapsing and expanding any item.
   */

  onKeydown(event) {
    switch (event.keyCode) {
      case KeyCodes.DOM_VK_UP:
        this.selectPreviousItem();
        break;

      case KeyCodes.DOM_VK_DOWN:
        this.selectNextItem();
        break;

      case KeyCodes.DOM_VK_RIGHT:
        if (this._selectedLabel.hasAttribute("expanded")) {
          this.selectNextItem();
        } else {
          this._selectedLabel.setAttribute("expanded""true");
        }
        break;

      case KeyCodes.DOM_VK_LEFT:
        if (
          this._selectedLabel.hasAttribute("expanded") &&
          !this._selectedLabel.hasAttribute("empty")
        ) {
          this._selectedLabel.removeAttribute("expanded");
        } else {
          this.selectPreviousItem();
        }
        break;

      default:
        return;
    }
    event.preventDefault();
  },

  /**
   * Scrolls the viewport of the tree so that the selected item is always
   * visible.
   */

  ensureSelectedVisible() {
    const { top, bottom } = this._selectedLabel.getBoundingClientRect();
    const height = this.root.children.parentNode.clientHeight;
    if (top < 0) {
      this._selectedLabel.scrollIntoView();
    } else if (bottom > height) {
      this._selectedLabel.scrollIntoView(false);
    }
  },
};

module.exports.TreeWidget = TreeWidget;

/**
 * Any item in the tree. This can be an empty leaf node also.
 *
 * @param {HTMLDocument} document
 *        The document element used for creating new nodes.
 * @param {TreeItem} parent
 *        The parent item for this item.
 * @param {string|DOMElement} label
 *        Either the dom node to be used as the item, or the string to be
 *        displayed for this node in the tree
 * @param {string} type
 *        The type of the current node. For ex. "js"
 */

function TreeItem(document, parent, label, type) {
  this.document = document;
  this.node = this.document.createElementNS(HTML_NS, "li");
  this.node.setAttribute("tabindex""0");
  this.isRoot = !parent;
  this.parent = parent;
  if (this.parent) {
    this.level = this.parent.level + 1;
  }
  if (label) {
    this.label = this.document.createElementNS(HTML_NS, "div");
    this.label.setAttribute("empty""true");
    this.label.setAttribute("level"this.level);
    this.label.className = "tree-widget-item";
    if (type) {
      this.label.setAttribute("type", type);
    }
    if (typeof label == "string") {
      this.label.textContent = label;
    } else {
      this.label.appendChild(label);
    }
    this.node.appendChild(this.label);
  }
  this.children = this.document.createElementNS(HTML_NS, "ul");
  if (this.isRoot) {
    this.children.className = "tree-widget-container";
  } else {
    this.children.className = "tree-widget-children";
  }
  this.node.appendChild(this.children);
  this.items = new Map();
}

TreeItem.prototype = {
  items: null,

  isSelected: false,

  expanded: false,

  isRoot: false,

  parent: null,

  children: null,

  level: 0,

  /**
   * Adds the item to the sub tree contained by this node. The item to be
   * inserted can be a direct child of this node, or further down the tree.
   *
   * @param {array} items
   *        Same as TreeWidget.add method's argument
   * @param {string} defaultType
   *        The default type of the item to be used when items[i].type is null
   * @param {boolean} sorted
   *        true if the tree items are inserted in a lexically sorted manner.
   *        Otherwise, false if the item are to be appended to their parent.
   */

  add(items, defaultType, sorted) {
    if (items.length == this.level) {
      // This is the exit condition of recursive TreeItem.add calls
      return;
    }
    // Get the id and label corresponding to this level inside the tree.
    const id = items[this.level].id || items[this.level];
    if (this.items.has(id)) {
      // An item with same id already exists, thus calling the add method of
      // that child to add the passed node at correct position.
      this.items.get(id).add(items, defaultType, sorted);
      return;
    }
    // No item with the id `id` exists, so we create one and call the add
    // method of that item.
    // The display string of the item can be the label, the id, or the item
    // itself if its a plain string.
    let label =
      items[this.level].label || items[this.level].id || items[this.level];
    const node = items[this.level].node;
    if (node) {
      // The item is supposed to be a DOMNode, so we fetch the textContent in
      // order to find the correct sorted location of this new item.
      label = node.textContent;
    }
    const treeItem = new TreeItem(
      this.document,
      this,
      node || label,
      items[this.level].type || defaultType
    );

    treeItem.add(items, defaultType, sorted);
    treeItem.node.setAttribute(
      "data-id",
      JSON.stringify(
        items.slice(0, this.level + 1).map(item => item.id || item)
      )
    );

    if (sorted) {
      // Inserting this newly created item at correct position
      const nextSibling = [...this.items.values()].find(child => {
        return child.label.textContent >= label;
      });

      if (nextSibling) {
        this.children.insertBefore(treeItem.node, nextSibling.node);
      } else {
        this.children.appendChild(treeItem.node);
      }
    } else {
      this.children.appendChild(treeItem.node);
    }

    if (this.label) {
      this.label.removeAttribute("empty");
    }
    this.items.set(id, treeItem);
  },

  /**
   * If this item is to be removed, then removes this item and thus all of its
   * subtree. Otherwise, call the remove method of appropriate child. This
   * recursive method goes on till we have reached the end of the branch or the
   * current item is to be removed.
   *
   * @param {array} items
   *        Ids of items leading up to the item to be removed.
   */

  remove(items = []) {
    const id = items.shift();
    if (id && this.items.has(id)) {
      const deleted = this.items.get(id);
      if (!items.length) {
        this.items.delete(id);
      }
      if (this.items.size == 0) {
        this.label.setAttribute("empty""true");
      }
      deleted.remove(items);
    } else if (!id) {
      this.destroy();
    }
  },

  /**
   * If this item is to be selected, then selected and expands the item.
   * Otherwise, if a child item is to be selected, just expands this item.
   *
   * @param {array} items
   *        Ids of items leading up to the item to be selected.
   */

  setSelectedItem(items) {
    if (!items[this.level]) {
      this.label.classList.add("theme-selected");
      this.label.setAttribute("expanded""true");
      return this.label;
    }
    if (this.items.has(items[this.level])) {
      const label = this.items.get(items[this.level]).setSelectedItem(items);
      if (label && this.label) {
        this.label.setAttribute("expanded"true);
      }
      return label;
    }
    return null;
  },

  /**
   * Collapses this item and all of its sub tree items
   */

  collapseAll() {
    if (this.label) {
      this.label.removeAttribute("expanded");
    }
    for (const child of this.items.values()) {
      child.collapseAll();
    }
  },

  /**
   * Expands this item and all of its sub tree items
   */

  expandAll() {
    if (this.label) {
      this.label.setAttribute("expanded""true");
    }
    for (const child of this.items.values()) {
      child.expandAll();
    }
  },

  destroy() {
    this.children.remove();
    this.node.remove();
    this.label = null;
    this.items = null;
    this.children = null;
  },
};

92%


¤ Dauer der Verarbeitung: 0.4 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.