B4J Tutorial [Web] Unit Testing a JavaScript Web Component with Github Copilot and NodeJS

Hi Fam

Source Code

Well I have been creating a treeview component for DaisyUI from scratch using Github Copilot. So far this is the final code. This component did not exist and I need it so why not.

B4X:
const DEFAULTS = {
  data: [],
  expandIconUrl: "./assets/chevron-down-solid.svg",
  collapseIconUrl: "./assets/chevron-right-solid.svg",
  blankIconUrl: "./assets/blank.svg",
  hasCheckbox: false,
  treeName: "treeView", // Changed to camelCase
  multipleSelect: false,
  multipleCheck: false,
  iconHeight: "16px",
  iconWidth: "16px",
  inlineEdit: false,
  dragNDrop: false,
  itemColor: 'primary',
  itemActiveColor: '',
  itemFocusColor: '',
  itemHoverColor: '',
  UseLocalstorage: true,
  replace: false,
  checkBoxSize: "md",
  textBoxSize: "sm",
  checkBoxActiveColor: "",
  checkBoxActiveBorderColor: "",
};
/**
 * DaisyUITreeView is a customizable tree view component that supports features like
 * drag-and-drop, inline editing, multi-selection, and dynamic node management.
 * It allows developers to create interactive and accessible tree structures.
 */
class DaisyUITreeView {
  /**
   * Creates an instance of DaisyUITreeView.
   * @param {HTMLElement} container - The container element where the tree view will be rendered.
   * @param {Object} [options={}] - Configuration options for the tree view.
   * @param {Array} [options.data=[]] - Initial data for the tree structure.
   * @param {string} [options.expandIconUrl] - URL for the expand icon.
   * @param {string} [options.collapseIconUrl] - URL for the collapse icon.
   * @param {boolean} [options.hasCheckbox=false] - Whether nodes should have checkboxes.
   * @param {string} [options.treeName="treeView"] - Unique name for the tree view.
   * @param {boolean} [options.multipleSelect=false] - Whether multiple nodes can be selected.
   * @param {boolean} [options.multipleCheck=false] - Whether multiple nodes can be checked.
   * @param {string} [options.iconHeight="16px"] - Height of the icons.
   * @param {string} [options.iconWidth="16px"] - Width of the icons.
   * @param {boolean} [options.inlineEdit=false] - Whether inline editing is enabled.
   * @param {boolean} [options.dragNDrop=false] - Whether drag-and-drop is enabled.
   * @param {string} [options.itemColor=""] - CSS class for item color.
   * @param {string} [options.itemActiveColor=""] - CSS class for active item color.
   * @param {string} [options.itemFocusColor=""] - CSS class for focused item color.
   * @param {string} [options.itemHoverColor=""] - CSS class for hover item color.
   */
  constructor(container, options = {}) {
    this.element = container;
    this.settings = { ...DEFAULTS, ...options };
    this.treeName = this._normalizeId(this.settings.treeName);
    this.tree = [];
    this.nodeMap = new Map();
    this._checkedNodes = new Set();
    this._selectedNodes = new Set();
    this._visibleNodes = new Set();
    this._activeColor = this.fixColor('checked:bg', this.settings.checkBoxActiveColor);
    this._activeBorderColor = this.fixColor('checked:border', this.settings.checkBoxActiveBorderColor);
    this._inputColorClass = `input-${this.settings.itemColor}`;
    this._checkColorClass = `checkbox-${this.settings.itemColor}`;
    this._inputSizeClass = `input-${this.settings.textBoxSize}`;
    this._checkSizeClass = `checkbox-${this.settings.checkBoxSize}`;
    if (this.settings.data) {
      let data = Array.isArray(this.settings.data)
        ? JSON.parse(JSON.stringify(this.settings.data))
        : [];
      this.tree = data;
      delete this.settings.data;
    }
    this._refresh();
    if (this.settings.dragNDrop) this._enableDragAndDrop();
    this.element.addEventListener("blur", this._onBlur.bind(this), true);
    this.element.addEventListener("keydown", this._onKeydown.bind(this));
    this.element.addEventListener("click", (e) => this._onClick(e));
    const style = document.createElement('style');
    style.innerHTML = `.xsummary::after { display: none !important; }`;
    document.head.appendChild(style);
  }
  _toggleVisibility(element, show) {
    if (!element) return;
    if (show) {
      element.classList.remove("hidden");
    } else {
      element.classList.add("hidden");
    }
  }
  _getElementById(id) {
    return document.getElementById(id);
  }
  _onCheckboxChange(nodeId, checked) {
    if (!this.nodeExists(nodeId)) return;
    const node = this.findNode(nodeId);
    // Update the checked state of the node
    this.checkNode(nodeId, checked);
    // Dispatch a custom event to notify about the checkbox change
    this._dispatchEvent("nodeChecked", { node, checked });
  }
fixColor(prefix, suffix) {
    // Treat null or undefined as blank
    prefix = prefix == null ? '' : String(prefix);
    suffix = suffix == null ? '' : String(suffix);
    if (!prefix || !suffix) {
        return '';
    }
    if (suffix.startsWith('#')) {
        suffix = `[${suffix}]`;
    }
    if (prefix === 'btn' || prefix === 'badge') {
        prefix = 'bg';
    }
    const result = `${prefix}-${suffix}`;
    if (result.endsWith('-')) {
        return '';
    }
    return result;
  }
   /**
   * Collapses all nodes in the tree view.
   */
  collapseAll() {
    const collapseRecursive = (nodes) => {
      for (const node of nodes) {
        this._collapseNode(node.nodeId);
        if (node.nodes) collapseRecursive(node.nodes);
      }
    };
    collapseRecursive(this.tree);
  }
    /**
   * Expands all nodes in the tree view.
   */
  expandAll() {
    const expandRecursive = (nodes) => {
      for (const node of nodes) {
        this._expandNode(node.nodeId);
        if (node.nodes) expandRecursive(node.nodes); // Recursively expand child nodes
      }
    };
    expandRecursive(this.tree);
  }
  _refresh() {
    const fragment = document.createDocumentFragment();
    this._initializeData({ nodes: this.tree });
    this._build(fragment, this.tree);
    this.element.innerHTML = ""; // Clear only once
    this.element.appendChild(fragment);
  }
  _initializeData(context) {
    if (!context.nodes) return;
    for (const node of context.nodes) {
      node.nodeId = this._normalizeId(node.nodeId);
      node.parentId = this._normalizeId(node.parentId);
      this._visibleNodes.add(node.nodeId);
      this.nodeMap.set(node.nodeId, node);
      if (node.nodes) this._initializeData({ nodes: node.nodes });
    }
  }
  _build(container, nodeList) {
    for (const node of nodeList) {
      const item = document.createElement("li");
      item.id = `${this.treeName}-${node.nodeId}`;
      item.classList.add("list-item");
      item.dataset.id = node.nodeId;
      item.setAttribute("role", "treeitem");
      item.setAttribute("aria-expanded", node.expanded || false);
      if (this.settings.dragNDrop) item.setAttribute("draggable", "true");
      item.setAttribute(
        "aria-selected",
        this._selectedNodes.has(node.nodeId) || false
      );
      if (this.settings.itemColor !== '') item.classList.add(this.settings.itemColor);
      if (this.settings.itemActiveColor !== '') item.classList.add(this.settings.itemActiveColor);
      if (this.settings.itemFocusColor !== '') item.classList.add(this.settings.itemFocusColor);
      if (this.settings.itemHoverColor !== '') item.classList.add(this.settings.itemHoverColor);
      if (!this._visibleNodes.has(node.nodeId)) {
        this._toggleVisibility(item, false);
      }
      if (this._selectedNodes.has(node.nodeId)) {
        item.classList.add("menu-active");
      }
      if (node.disabled) {
        item.setAttribute("aria-disabled", "true");
        item.classList.add("menu-disabled");
      }
          
      if (node.nodes && node.nodes.length > 0) {
        const details = document.createElement("details");
        details.id = `${this.treeName}-${node.nodeId}-details`;
        details.dataset.id = node.nodeId;
        details.classList.add("xdetails");
        item.classList.add("xdetails");
        if (node.expanded) details.setAttribute("open", "");
        const summary = document.createElement("summary");
        summary.id = `${this.treeName}-${node.nodeId}-summary`;
        summary.dataset.id = node.nodeId;
        summary.classList.add("xsummary");     
       
        const hostdiv = document.createElement("div");
        hostdiv.id = `${this.treeName}-${node.nodeId}-div`;
        hostdiv.classList.add("flex", "items-center", "gap-3", "w-full", "xdiv");
       
        if (node.iconUrl === "") node.iconUrl = this.settings.collapseIconUrl;
        if (node.iconUrl) {
          const icon = document.createElement("svg-renderer");
          icon.setAttribute("replace", this.settings.replace);
          icon.id = `${this.treeName}-${node.nodeId}-icon`;
          icon.dataset.id = node.nodeId;
          icon.setAttribute("use-localstorage", this.settings.UseLocalstorage);
          icon.dataset.src = node.expanded
            ? this.settings.expandIconUrl
            : this.settings.collapseIconUrl;
          icon.classList.add("state-icon", "xicon");
          icon.setAttribute("style", "pointer-events:none; min-height:" + this.settings.iconHeight + "; min-width:" + this.settings.iconWidth + ";"); // Updated
          icon.setAttribute("fill", "currentColor"); // Updated
          icon.setAttribute("data-js", "enabled"); // Updated
          icon.setAttribute("width", this.settings.iconWidth);
          icon.setAttribute("height", this.settings.iconHeight);
          hostdiv.appendChild(icon);
        }
        if (this.settings.hasCheckbox) {
          const checkbox = document.createElement("input");
          checkbox.id = `${this.treeName}-${node.nodeId}-check`;
          checkbox.type = "checkbox";
          checkbox.classList.add("checkbox");
          if (this._checkColorClass !== '') checkbox.classList.add(this._checkColorClass);
          if (this._activeColor !== '') checkbox.classList.add(this._activeColor);
          if (this._activeBorderColor !== '') checkbox.classList.add(this._activeBorderColor);
          if (this._checkSizeClass !== '') checkbox.classList.add(this._checkSizeClass);
          checkbox.dataset.id = node.nodeId;
          checkbox.checked = this._checkedNodes.has(node.nodeId);
          hostdiv.appendChild(checkbox);
        }
        const txtBox = document.createElement("input");
        txtBox.id = `${this.treeName}-${node.nodeId}-input`;
        txtBox.type = "text";
        txtBox.value = node.text;
        txtBox.classList.add(
          "input",
          "input-ghost",
          "hidden",
          "xinput",
          "w-full"
        );
        if (this._inputColorClass !== '') txtBox.classList.add(this._inputColorClass);  
        if (this._inputSizeClass !== '') txtBox.classList.add(this._inputSizeClass);      
        txtBox.dataset.id = node.nodeId;
        hostdiv.appendChild(txtBox);
        const textNode = document.createElement("span");
        textNode.id = `${this.treeName}-${node.nodeId}-text`;
        textNode.textContent = node.text;
        textNode.dataset.id = node.nodeId;
        textNode.classList.add("xspan");
        hostdiv.appendChild(textNode);
        summary.appendChild(hostdiv);
        details.appendChild(summary);
        const nestedList = document.createElement("ul");
        nestedList.id = `${this.treeName}-${node.nodeId}-ul`;
        nestedList.dataset.id = node.nodeId;
        this._build(nestedList, node.nodes);
        details.appendChild(nestedList);
        item.appendChild(details);
      } else {
        // div class="flex items-center gap-3 w-full"
        const link = document.createElement("div");
        link.id = `${this.treeName}-${node.nodeId}-anchor`;
        link.dataset.id = node.nodeId;
        link.classList.add("xanchor", "flex", "items-center", "gap-3", "w-full");
        if (this.settings.itemColor !== '') link.classList.add(this.settings.itemColor);
        if (this.settings.itemActiveColor !== '') link.classList.add(this.settings.itemActiveColor);
        if (this.settings.itemFocusColor !== '') link.classList.add(this.settings.itemFocusColor);
        if (this.settings.itemHoverColor !== '') link.classList.add(this.settings.itemHoverColor);
        if (node.href) {
          link.setAttribute("href", node.href);
        }
        if (node.iconUrl === "") node.iconUrl = this.settings.blankIconUrl;
        if (node.iconUrl) {
          const icon = document.createElement("svg-renderer");
          icon.setAttribute("replace", this.settings.replace);
          icon.setAttribute("use-localstorage", this.settings.UseLocalstorage);
          icon.dataset.id = node.nodeId;
          icon.dataset.src = node.iconUrl;
          icon.id = `${this.treeName}-${node.nodeId}-icon`;
          icon.classList.add("state-icon");
          icon.setAttribute("style", "pointer-events:none; min-height:" + this.settings.iconHeight + "; min-width:" + this.settings.iconWidth + ";"); // Updated
          icon.setAttribute("fill", "currentColor"); // Updated
          icon.setAttribute("data-js", "enabled"); // Updated
          icon.setAttribute("width", this.settings.iconWidth);
          icon.setAttribute("height", this.settings.iconHeight);
          link.appendChild(icon);
        }
        if (this.settings.hasCheckbox) {
          const checkbox = document.createElement("input");
          checkbox.id = `${this.treeName}-${node.nodeId}-check`;
          checkbox.type = "checkbox";
          checkbox.classList.add("checkbox");
          checkbox.dataset.id = node.nodeId;
          checkbox.checked = this._checkedNodes.has(node.nodeId);
          if (this._checkColorClass !== '') checkbox.classList.add(this._checkColorClass);
          if (this._activeColor !== '') checkbox.classList.add(this._activeColor);
          if (this._activeBorderColor !== '') checkbox.classList.add(this._activeBorderColor);
          if (this._checkSizeClass !== '') checkbox.classList.add(this._checkSizeClass);
          link.appendChild(checkbox);
        }
        const txtBox = document.createElement("input");
        txtBox.id = `${this.treeName}-${node.nodeId}-input`;
        txtBox.type = "text";
        txtBox.value = node.text;
        txtBox.classList.add(
          "input",
          "input-ghost",
          "hidden",
          "w-full",
          "xinput"
        );
        if (this._inputColorClass !== '') txtBox.classList.add(this._inputColorClass);  
        if (this._inputSizeClass !== '') txtBox.classList.add(this._inputSizeClass);
        txtBox.dataset.id = node.nodeId;
        link.appendChild(txtBox);
        const textNode = document.createElement("span");
        textNode.id = `${this.treeName}-${node.nodeId}-text`;
        textNode.textContent = node.text;
        textNode.dataset.id = node.nodeId;
        textNode.classList.add("xspan");
        link.appendChild(textNode);
        item.appendChild(link);
      }
      container.appendChild(item);
    }
  }
  _onClick(e) {
    // if we checked a checkbox, then exit
    const checkbox = e.target.closest(".checkbox");
    if (checkbox) {
      e.stopPropagation(); // Prevent further propagation
      const nodeId = this._normalizeId(checkbox.dataset.id);
      if (this.nodeExists(nodeId)) {
        const checked = checkbox.checked;
        this._onCheckboxChange(nodeId, checked); // Handle checkbox logic
      }
      return; // Exit to prevent firing other events
    }
    const xsummary = e.target.closest(".xsummary");
    if (xsummary) {
      e.stopPropagation(); // Prevent further propagation
      const id = this._normalizeId(xsummary.dataset.id);
      if (!this.nodeExists(id)) return;
      const node = this.findNode(id);
      const details = this._getElementById(`${this.treeName}-${id}-details`);
      let show = details.hasAttribute("open");
      show = !show;
      node.expanded = show;
      const evt = show ? "nodeExpanded" : "nodeCollapsed";
      const icon = this._getElementById(`${this.treeName}-${id}-icon`);
      if (icon) {
        if (show) {
          icon.setAttribute("data-src", this.settings.expandIconUrl);
        } else {
          icon.setAttribute("data-src", this.settings.collapseIconUrl);
        }
        node.iconUrl = icon.getAttribute("data-src");
      }
      this._dispatchEvent(evt, { node });
      return;
    }
    const xspan = e.target.closest(".xspan");
    if (xspan) {
      e.stopPropagation(); // Prevent further propagation
      const id = this._normalizeId(xspan.dataset.id);
      if (!this.nodeExists(id)) return;
      const node = this.findNode(id);
      if (
        this.settings.inlineEdit &&
        e.target.id === `${this.treeName}-${id}-text`
      ) {
        this._enableInlineEdit(id); // Activate inline editing
        return; // Exit to prevent firing nodeClick
      }
      // Fire nodeClick event
      this._dispatchEvent("nodeClick", { node });
      return;
    }
    const xanchor = e.target.closest(".xanchor");
    if (xanchor) {
      e.stopPropagation(); // Prevent further propagation
      const id = this._normalizeId(xanchor.dataset.id);
      if (!this.nodeExists(id)) return;
      const node = this.findNode(id);
      if (this.settings.inlineEdit) {
        this._enableInlineEdit(id); // Activate inline editing
        return; // Exit to prevent firing nodeClick
      }
      // Fire nodeClick event
      this._dispatchEvent("nodeClick", { node });
      return;
    }
  }
  _enableDragAndDrop() {
    this.element.addEventListener("dragend", (e) => {
      const item = this.element.querySelector(".drag-over");
      if (item) item.classList.remove("drag-over");
    });
    this.element.addEventListener("dragstart", (e) => {
      const item = e.target.closest(".list-item");
      if (item) e.dataTransfer.setData("id", item.dataset.id);
    });
    this.element.addEventListener("dragover", (e) => {
      e.preventDefault();
      const item = e.target.closest(".list-item");
      if (item) item.classList.add("drag-over");
    });
    this.element.addEventListener("dragleave", (e) => {
      const item = e.target.closest(".list-item");
      if (item) item.classList.remove("drag-over");
    });
    this.element.addEventListener("drop", (e) => {
      e.preventDefault();
      const targetItem = e.target.closest(".list-item");
      if (!targetItem) return;
      const draggedNodeId = e.dataTransfer.getData("id");
      const targetNodeId = targetItem.dataset.id;
      if (draggedNodeId === targetNodeId) return; // Prevent dropping onto itself
      const draggedNode = this.findNode(draggedNodeId);
      const targetNode = this.findNode(targetNodeId);
      let parent = targetNode;
      while (parent) {
        if (parent.nodeId === draggedNodeId) return; // Prevent dropping onto descendants
        parent = this.findNode(parent.parentId);
      }
      if (draggedNode && targetNode) {
        // Enhancement: Dispatch custom events for drag-and-drop actions
        this._dispatchEvent("nodeDragStart", { draggedNode });
        this._dispatchEvent("nodeDrop", { draggedNode, targetNode });
        this.removeNode(draggedNodeId);
        this.addNode(
          targetNodeId,
          draggedNode.nodeId,
          draggedNode.iconUrl,
          draggedNode.text,
          draggedNode.href,
          draggedNode.hasCheckbox
        );
        this._refresh();
      }
    });
  }
  _normalizeId(id) {
    return (id ?? "").toString().trim().toLowerCase();
  }
  _cstr(id) {
    return (id ?? "").toString();
  }
  nodeExists(nodeId) {
    const normalizedId = this._normalizeId(nodeId);
    if (normalizedId === "") return false;
    return this.nodeMap.has(normalizedId);
  }
  findNode(nodeId) {
    const normalizedId = this._normalizeId(nodeId);
    if (normalizedId === "") return null; // Return null if the normalized ID is empty
    return this.nodeMap.get(normalizedId) || null; // Return the node or null if not found
  }
    /**
   * Adds a new node to the tree.
   * @param {string} parentId - The ID of the parent node.
   * @param {string} nodeId - The ID of the new node.
   * @param {string} [iconUrl=""] - URL for the node's icon.
   * @param {string} text - The text content of the node.
   * @param {string} [href=""] - The hyperlink for the node.
   */
  addNode(parentId, nodeId, iconUrl = "", text, href = "") {
    parentId = this._normalizeId(parentId);
    nodeId = this._normalizeId(nodeId);
    iconUrl = this._cstr(iconUrl);
    text = this._cstr(text);
    href = this._cstr(href);
    //
    if (nodeId === "") return;
    if (this.nodeExists(nodeId)) return;
 
    const hasCheckbox = this.settings.hasCheckbox || false;
    const newNode = { nodeId, parentId, iconUrl, text, href, hasCheckbox };
    if (parentId === "") {
      this.tree.push(newNode);
    } else {
      const parent = this.findNode(parentId);
      if (parent) {
        parent.nodes = parent.nodes || [];
        parent.nodes.push(newNode);
        this.nodeMap.set(parentId, parent);
      }
    }
    this.nodeMap.set(nodeId, newNode); // Add to node map
    this._visibleNodes.add(nodeId);
  }
  findNodeRecursive(tree, nodeId) {
    for (const node of tree) {
      if (node.nodeId === nodeId) {
        return node; // Node found
      }
      if (node.nodes && node.nodes.length > 0) {
        const result = findNodeRecursive(node.nodes, nodeId); // Recursively search in child nodes
        if (result) {
          return result; // Return the node if found in children
        }
      }
    }
    return null; // Node not found
  }
    /**
   * Hides a node and its children from the tree view.
   * @param {string} nodeId - The ID of the node to hide.
   */
  hideNode(nodeId) {
    if (!this.nodeExists(nodeId)) return;
    const node = this.findNode(nodeId);
    const hideChildren = (node) => {
      if (!node) return;
      this._visibleNodes.delete(node.nodeId);
      node.visible = false;
      const item = this._getElementById(`${this.treeName}-${node.nodeId}`);
      if (item) this._toggleVisibility(item, false);
      if (node.nodes) {
        node.nodes.forEach((child) => hideChildren(child));
      }
    };
    hideChildren(node);
  }
    /**
   * Shows a previously hidden node and its children.
   * @param {string} nodeId - The ID of the node to show.
   */
  showNode(nodeId) {
    if (!this.nodeExists(nodeId)) return;
    const node = this.findNode(nodeId);
    const showChildren = (node) => {
      if (!node) return;
      this._visibleNodes.add(node.nodeId);
      node.visible = true;
      const item = this._getElementById(`${this.treeName}-${node.nodeId}`);
      if (item) this._toggleVisibility(item, true);
      if (node.nodes) {
        node.nodes.forEach((child) => showChildren(child));
      }
    };
    showChildren(node);
  }
    /**
   * Checks or unchecks a node and its children.
   * @param {string} nodeId - The ID of the node to check or uncheck.
   * @param {boolean} state - The checked state (true for checked, false for unchecked).
   */
  checkNode(nodeId, state) {
    if (!this.nodeExists(nodeId)) return;
    const node = this.findNode(nodeId);
    const checkChildren = (node, state) => {
      if (!node) return;
      if (state) {
        this._checkedNodes.add(node.nodeId);
      } else {
        this._checkedNodes.delete(node.nodeId);
      }
      node.checked = state;
      const check = this._getElementById(
        `${this.treeName}-${node.nodeId}-check`
      );
      if (check) check.checked = state;
      if (node.nodes) {
        node.nodes.forEach((child) => checkChildren(child, state));
      }
    };
    checkChildren(node, state);
  }
  /**
 * Expands or collapses a specific node in the tree view.
 * @param {string} nodeId - The ID of the node to expand or collapse.
 * @param {boolean} state - The expanded state (true for expanded, false for collapsed).
 */
  expandNode(nodeId, state) {
    if (state) this._expandNode(nodeId);
    else this._collapseNode(nodeId);
  }
    /**
   * Removes a node from the tree.
   * @param {string} nodeId - The ID of the node to remove.
   */
  removeNode(nodeId) {
    nodeId = this._normalizeId(nodeId);
    if (!this.nodeExists(nodeId)) return;
 
    const node = this.findNode(nodeId);
 
    // Remove the node from its parent's nodes array
    if (node.parentId) {
      const parent = this.findNode(node.parentId);
      if (parent && parent.nodes) {
        parent.nodes = parent.nodes.filter((child) => child.nodeId !== nodeId);
      }
    } else {
      // If the node has no parent, it's a root node, so remove it from the tree
      this.tree = this.tree.filter((rootNode) => rootNode.nodeId !== nodeId);
    }
 
    // Remove the node from the nodeMap
    this.nodeMap.delete(nodeId);
 
    // Remove the node from the DOM
    const item = this._getElementById(`${this.treeName}-${nodeId}`);
    if (item) item.remove();
 
    // Remove the node from the visible and selected sets
    this._visibleNodes.delete(nodeId);
    this._selectedNodes.delete(nodeId);
    this._checkedNodes.delete(nodeId);
  }
  _expandNode(nodeId) {
    if (!this.nodeExists(nodeId)) return;
    const node = this.findNode(nodeId);
    node.expanded = true;
    const details = this._getElementById(
      `${this.treeName}-${node.nodeId}-details`
    );
    if (details) details.setAttribute("open", "");
  }
  _collapseNode(nodeId) {
    if (!this.nodeExists(nodeId)) return;
    const node = this.findNode(nodeId);
    node.expanded = false;
    const details = this._getElementById(
      `${this.treeName}-${node.nodeId}-details`
    );
    if (details) details.removeAttribute("open");
  }
  /**
 * Enables or disables a specific node and its children.
 * @param {string} nodeId - The ID of the node to enable or disable.
 * @param {boolean} state - The enabled state (true for enabled, false for disabled).
 */
  enableNode(nodeId, state) {
    if (state) this._enableNode(nodeId);
    else this._disableNode(nodeId);
  }
  _enableNode(nodeId) {
    if (!this.nodeExists(nodeId)) return;
    const node = this.findNode(nodeId);
    const enableChildren = (node) => {
      if (!node) return;
      node.disabled = false;
      const item = this._getElementById(`${this.treeName}-${node.nodeId}`);
      if (item) item.classList.remove("menu-disabled");
      if (node.nodes) {
        node.nodes.forEach((child) => enableChildren(child));
      }
    };
    enableChildren(node);
  }
  _disableNode(nodeId) {
    if (!this.nodeExists(nodeId)) return;
    const node = this.findNode(nodeId);
    const disableChildren = (node) => {
      if (!node) return;
      node.disabled = true;
      const item = this._getElementById(`${this.treeName}-${node.nodeId}`);
      if (item) item.classList.add("menu-disabled");
      if (node.nodes) {
        node.nodes.forEach((child) => disableChildren(child));
      }
    };
    disableChildren(node);
  }
    /**
   * Clears all nodes and resets the tree view.
   */
  clear() {
    this.nodeMap.clear();
    this.tree = [];
    this._checkedNodes.clear();
    this._selectedNodes.clear(); // Clear all selected nodes
    this._visibleNodes.clear();
  }
    /**
   * Retrieves the children of a specific node.
   * @param {string} nodeId - The ID of the node to retrieve children for.
   * @returns {Array} An array of child nodes.
   */
  getChildren(nodeId) {
    if (!this.nodeExists(nodeId)) return [];
    const node = this.findNode(nodeId);
    return Array.isArray(node.nodes) ? [...node.nodes] : [];
  }
    /**
   * Removes all children of a specific node.
   * @param {string} nodeId - The ID of the node to remove children from.
   */
  removeChildren(nodeId) {
    if (!this.nodeExists(nodeId)) return;
    const node = this.findNode(nodeId);
    if (node.nodes) {
      node.nodes.forEach((child) => this._checkedNodes.delete(child.nodeId));
      node.nodes.forEach((child) => this._selectedNodes.delete(child.nodeId));
      node.nodes.forEach((child) => this._visibleNodes.delete(child.nodeId));
      node.nodes.forEach((child) => this.removeNode(child.nodeId));
    }
    node.nodes = [];
  }
    /**
   * Updates the properties of a node.
   * @param {string} nodeId - The ID of the node to update.
   * @param {string} iconUrl - The new icon URL for the node.
   * @param {string} text - The new text content for the node.
   * @param {string} [href=""] - The new hyperlink for the node.
   */
  updateNode(nodeId, iconUrl, text, href = "") {
    nodeId = this._normalizeId(nodeId);
    iconUrl = this._cstr(iconUrl);
    text = this._cstr(text);
    href = this._cstr(href);
    if (nodeId === "") return;
    if (!this.nodeExists(nodeId)) return;
    const node = this.findNode(nodeId);
    node.iconUrl = iconUrl;
    node.text = text;
    node.href = href;
    //
    const txt = this._getElementById(`${this.treeName}-${node.nodeId}-text`);
    if (txt) txt.textContent = text;
    const inp = this._getElementById(`${this.treeName}-${node.nodeId}-input`);
    if (inp) inp.value = text;
    const icon = this._getElementById(`${this.treeName}-${node.nodeId}-icon`);
    if (icon) icon.dataset.src = iconUrl;
  }
    /**
   * Selects or deselects a node and its children.
   * @param {string} nodeId - The ID of the node to select or deselect.
   * @param {boolean} state - The selected state (true for selected, false for deselected).
   */
  selectNode(nodeId, state) {
    if (!this.nodeExists(nodeId)) return;
    const node = this.findNode(nodeId);
    const selectChildren = (node, state) => {
      if (!node) return;
      if (state) {
        this._selectedNodes.add(node.nodeId);
      } else {
        this._selectedNodes.delete(node.nodeId);
      }
      node.selected = state;
      const item = this._getElementById(`${this.treeName}-${node.nodeId}`);
      if (item) {
        if (state) item.classList.add("menu-active");
        else item.classList.remove("menu-active");
      }
      if (node.nodes) {
        node.nodes.forEach((child) => selectChildren(child, state));
      }
    };
    selectChildren(node, state);
  }
    /**
   * Clears all selected nodes in the tree view.
   */
  clearSelected() {
    this._selectedNodes.forEach((child) =>
      this.selectNode(child.nodeId, false)
    );
    this._selectedNodes.clear(); // Clear all selected nodes
  }
    /**
   * Clears all checked nodes in the tree view.
   */
  clearChecked() {
    this._checkedNodes.forEach((child) => this.checkNode(child.nodeId, false));
    this._checkedNodes.clear(); // Clear all checked nodes
  }
    /**
   * Selects multiple nodes in the tree view.
   * @param {Array<string>} nodeIds - An array of node IDs to select.
   */
  selectNodes(nodeIds) {
    this._selectedNodes.forEach((child) =>
      this.selectNode(child.nodeId, false)
    );
    const normalizedIds = new Set(nodeIds.map((id) => this._normalizeId(id)));
    normalizedIds.forEach((child) => this.selectNode(child, true));
    //this._dispatchEvent('nodesSelected', { nodeIds: normalizedIds });
  }
    /**
   * Retrieves the currently selected node.
   * @param {boolean} [includeChildren=false] - Whether to include child nodes in the result.
   * @returns {Object|null} The selected node or null if no node is selected.
   */
  getSelectedNode(includeChildren = false) {
    const el = this.element.querySelector(".menu-active");
    if (!el) return null;
    const nodeId = this._normalizeId(el.dataset.id);
    const node = this.findNode(nodeId);
    if (!node || !includeChildren) return node;
    return { ...node, nodes: node.nodes ?? [] };
  }
    /**
   * Retrieves all selected nodes in the tree view.
   * @returns {Array} An array of selected node IDs.
   */
  getSelectedNodes() {
    // Return all selected nodes
    return Array.from(this._selectedNodes);
  }
    /**
   * Retrieves all checked nodes in the tree view.
   * @returns {Array} An array of checked node IDs.
   */
  getCheckedNodes() {
    return Array.from(this._checkedNodes);
  }
    /**
   * Checks multiple nodes in the tree view.
   * @param {Array<string>} nodeIds - An array of node IDs to check.
   */
  checkNodes(nodeIds) {
    this._checkedNodes.forEach((child) => this.checkNode(child.nodeId, false));
    const normalizedIds = new Set(nodeIds.map((id) => this._normalizeId(id)));
    normalizedIds.forEach((child) => this.checkNode(child, true));
    this._dispatchEvent("nodesChecked", { nodeIds: normalizedIds });
  }
    /**
   * Enables inline editing for a specific node.
   * @param {string} nodeId - The ID of the node to enable inline editing for.
   */
  enableInlineEditing(nodeId) {
    this._enableInlineEdit(nodeId);
  }
    /**
   * Refreshes the tree view by rebuilding its structure.
   */
  refresh() {
    this._refresh();
  }
  _dispatchEvent(eventName, detail) {
    const event = new CustomEvent(eventName, { detail });
    this.element.dispatchEvent(event);
  }
  // Event delegation for blur
  _onBlur(e) {
    const input = e.target.closest("input.xinput"); // Check if the event target is an input
    if (!input) return;
    const nodeId = this._normalizeId(input.dataset.id);
    const node = this.findNode(nodeId);
    if (!node) return;
    const txt = this._getElementById(`${this.treeName}-${nodeId}-text`);
    if (!txt) return;
    const originalText = node.text;
    if (input.value.trim() === "") {
      this._toggleVisibility(input, false);
      this._toggleVisibility(txt, true);
      this._dispatchEvent("nodeEditCancelled", { node }); // Notify about cancellation
      return;
    }
    if (input.value.trim() === originalText) {
      this._toggleVisibility(input, false);
      this._toggleVisibility(txt, true);
      this._dispatchEvent("nodeEditCancelled", { node }); // Notify about cancellation
      return;
    }
    // Update the node text
    node.text = input.value.trim();
    txt.textContent = input.value.trim();
    input.value = input.value.trim();
    this._toggleVisibility(input, false);
    this._toggleVisibility(txt, true);
    this._dispatchEvent("nodeEdited", { node });
  }
  // Event delegation for keydown
  _onKeydown(e) {
    const input = e.target.closest("input.xinput"); // Check if the event target is an input
    if (!input) return;
    const nodeId = this._normalizeId(input.dataset.id);
    const node = this.findNode(nodeId);
    if (!node) return;
    const txt = this._getElementById(`${this.treeName}-${nodeId}-text`);
    if (!txt) return;
    const originalText = node.text;
    if (e.key === "Enter") {
      input.blur(); // Trigger blur event to save changes
    } else if (e.key === "Escape") {
      // Revert changes
      node.text = originalText;
      txt.textContent = originalText;
      input.value = originalText;
      input.blur(); // Trigger blur to hide the input
    }
  }
  _enableInlineEdit(nodeId) {
    const node = this.findNode(nodeId);
    if (!node) return;
    const txt = this._getElementById(`${this.treeName}-${nodeId}-text`);
    const input = this._getElementById(`${this.treeName}-${nodeId}-input`);
    if (!txt || !input) return;
    // Set the input's value to the current text of the node
    input.value = node.text;
    // Toggle visibility for inline editing
    this._toggleVisibility(txt, false);
    this._toggleVisibility(input, true);
    input.focus();
  }
}
export default DaisyUITreeView;

And most of it was generated by AI and I just did some tweaks here and there to suit my final web component. I also told the thing to generate JSDoc related code for the component and injected it. I found out that in most cases, having descriptive names for your functions helps and it also studies your code to understand what it does.

To test the functionality of the web component, I installed jest on my project.


B4X:
import { JSDOM } from 'jsdom';
import DaisyUITreeView from '../src/DaisyUITreeView';
import '@testing-library/jest-dom';

describe('DaisyUITreeView', () => {
  let container;
  let treeView;
  let node;
  let dom;
  let document;
  beforeEach(() => {
    dom = new JSDOM(`<!DOCTYPE html><html><head></head><body><div id="tree"></div></body></html>`);
    global.document = dom.window.document;
    global.window = dom.window;
    document = dom.window.document;
    container = document.getElementById('tree');
    treeView = new DaisyUITreeView(container, {
      hasCheckbox: true,
      multipleSelect: false,
      multipleCheck: false,
      inlineEdit: false,
    });
    treeView.clear();
    treeView.addNode('', 'a', '', 'Node A');
    treeView.addNode('a', 'aa', '', 'Node AA');
    treeView.addNode('', 'b', '', 'Node B');
    treeView.refresh();
    //console.log(dom.serialize());
  });
  afterEach(() => {
    container = null;
    treeView = null;
    delete global.document;
    delete global.window;
  });
 
  it('should initialize with default settings', () => {
    expect(treeView.settings.hasCheckbox).toBe(true);
    expect(treeView.settings.multipleSelect).toBe(true);
  });
  it('should add a new node', () => {   
    treeView.addNode('', 'c', '', 'Node C');
    treeView.refresh();
    node = treeView.findNode('c');
    expect(node).not.toBeNull();
    expect(node.text).toBe('Node C');
  });
  it('should remove a node', () => {
    treeView.addNode('', 'c', '', 'Node C');
    treeView.refresh();
    treeView.removeNode('c')
    expect(treeView.nodeExists('c')).toBe(false);
  });
  it('should expand and collapse a node', () => {
    treeView.expandNode('a', true);
    expect(treeView.findNode('a').expanded).toBe(true);
    treeView.expandNode('a', false);
    expect(treeView.findNode('a').expanded).toBe(false);
  });

  it('should select and deselect a node', () => {
    treeView.selectNode('a', true);
    expect(treeView.getSelectedNode().nodeId).toBe('a');
    const nodeA = document.getElementById(`${treeView.treeName}-a`);
    expect(nodeA.classList.contains('menu-active')).toBe(true);
    treeView.selectNode('a', false);
    node = treeView.getSelectedNode();
    expect(node).toBeNull();
  });
  it('should check and uncheck nodes', () => {
    treeView.checkNode('a', true);
    expect(treeView.getCheckedNodes()).toContain('a');
    expect(treeView.getCheckedNodes()).toContain('aa');
    treeView.checkNode('a', false);
    expect(treeView.getCheckedNodes()).not.toContain('a');
    expect(treeView.getCheckedNodes()).not.toContain('aa');
  });
  it('should update node text', () => {
    treeView.updateNode('a', '', 'Updated Node A', '');
    expect(treeView.findNode('a').text).toBe('Updated Node A');
  });
  it('should enforce multiple select using selectNode', () => {
    treeView.selectNode('a', true);
    treeView.selectNode('b', true);
    expect(treeView.getSelectedNodes().length).toBe(3);
  });
  it('should handle parent-child checkbox updates', () => {
    treeView.addNode('', 'parent', '', 'Parent Node');
    treeView.addNode('parent', 'child', '', 'Child Node');
    treeView.refresh();
    treeView.checkNode('parent', true);
    expect(treeView.getCheckedNodes()).toContain('parent');
    expect(treeView.getCheckedNodes()).toContain('child');
  });
  it('should expand all nodes', () => {
    treeView.expandAll();
    expect(treeView.tree.every(node => node.expanded)).toBe(true);
  });
  it('should collapse all nodes', () => {
    treeView.expandAll();
    treeView.collapseAll();
    expect(treeView.tree.every(node => !node.expanded)).toBe(true);
  });
  it('should disable and enable a node', () => {
    treeView.enableNode('a', false);
    const node = treeView.findNode('a');
    expect(node.disabled).toBe(true);
    treeView.enableNode('a', true);
    expect(node.disabled).toBe(false);
  });
  it('should select multiple nodes using selectNodes', () => {
    treeView.selectNodes(['a', 'b']);
    const selectedNodes = treeView.getSelectedNodes();
    expect(selectedNodes).toEqual(['a', 'aa', 'b']);
});
it('should return an empty array if no nodes are selected', () => {
    const selectedNodes = treeView.getSelectedNodes();
    expect(selectedNodes).toEqual([]);
});
it('should update selected nodes when selectNodes is called again', () => {
    treeView.addNode('', 'c', '', 'Node C');
    treeView.refresh();
    treeView.selectNodes(['a']);
    treeView.selectNodes(['b', 'c']);
    const selectedNodes = treeView.getSelectedNodes();
    expect(selectedNodes).toEqual(['a', 'aa', 'b', 'c']);
});
  it('should clear all nodes', () => {
    treeView.clear();
    treeView.refresh();
    expect(treeView.tree.length).toBe(0);
    expect(treeView.getCheckedNodes().length).toBe(0);
    expect(treeView.getSelectedNodes().length).toBe(0);
  });
  it('should hide a node and its children', () => {
    treeView.hideNode('a');
    const nodeA = document.getElementById(`${treeView.treeName}-a`);
    const nodeAA = document.getElementById(`${treeView.treeName}-aa`);
    expect(nodeA.classList.contains('hidden')).toBe(true);
    expect(nodeAA.classList.contains('hidden')).toBe(true);
   
    expect(treeView.findNode('a').visible).toBe(false);
    expect(treeView.findNode('aa').visible).toBe(false);
  });
  it('should show a node and its children', () => {
    treeView.hideNode('a'); // First hide the node
    treeView.showNode('a'); // Then show it again
    const nodeA = document.getElementById(`${treeView.treeName}-a`);
    const nodeAA = document.getElementById(`${treeView.treeName}-aa`);
    expect(nodeA.classList.contains('hidden')).toBe(false);
    expect(nodeAA.classList.contains('hidden')).toBe(false);
    expect(treeView.findNode('a').visible).toBe(true);
    expect(treeView.findNode('aa').visible).toBe(true);
  });
  it('should not throw an error when hiding a non-existent node', () => {
    expect(() => treeView.hideNode('nonexistent')).not.toThrow();
  });
  it('should not throw an error when showing a non-existent node', () => {
    expect(() => treeView.showNode('nonexistent')).not.toThrow();
  });
});

The code for the unit test was also generated by copit, I just requested it to generate a unit test project of the component taking into consideration all use cases. Anyway, one can tell it do to anything, but still one needs to quality check the source code it generates a great deal. One mistake I made was to ask it to generate code and then I click replace. Some of the good code I had already done was deleted and I had to somehow revert back changes. So in most cases, I just copy the code I need to the target file myself.

The nice thing is, even when you run into an error, you can ask it to explain what it means and find solutions for you.

Sadly explaining how all of this works is beyond the scope of this tutorial. One needs to understand NodeJS and how to install packages etc. Mostly the copilot will give instructions on what to do and how.
 

Mashiane

Expert
Licensed User
Longtime User
So SithasoDaisy5 has a new TreeView component, which was almost all done via AI.

Adopted to B4x, the complete source code of the custom view is..

B4X:
#IgnoreWarnings:12
#Event: nodeDrop (e As BANanoEvent)
#Event: nodeDragStart (e As BANanoEvent)
#Event: nodeExpanded (e As BANanoEvent)
#Event: nodeCollapsed (e As BANanoEvent)
#Event: nodeEdited (e As BANanoEvent)
#Event: nodeSelected (e As BANanoEvent)
#Event: nodesSelected (e As BANanoEvent)
#Event: nodesChecked (e As BANanoEvent)
#Event: nodeClick (e As BANanoEvent)
#Event: nodeEditCancelled (e As BANanoEvent)

#DesignerProperty: Key: ParentID, DisplayName: ParentID, FieldType: String, DefaultValue: , Description: The ParentID of this component
#DesignerProperty: Key: ItemColor, DisplayName: Item Color, FieldType: String, DefaultValue: primary, Description: Item Color
#DesignerProperty: Key: ItemActiveColor, DisplayName: Item Active Color, FieldType: String, DefaultValue: , Description: Item Active Color
#DesignerProperty: Key: ItemFocusColor, DisplayName: Item Focus Color, FieldType: String, DefaultValue: , Description: Item Focus Color
#DesignerProperty: Key: ItemHoverColor, DisplayName: Item Hover Color, FieldType: String, DefaultValue: , Description: Item Hover Color
#DesignerProperty: Key: Height, DisplayName: Height, FieldType: String, DefaultValue: full, Description: Height
#DesignerProperty: Key: Width, DisplayName: Width, FieldType: String, DefaultValue: 200px, Description: Width
#DesignerProperty: Key: CollapseIconUrl, DisplayName: Collapse Icon Url, FieldType: String, DefaultValue: ./assets/chevron-right-solid.svg, Description: Collapse Icon Url
#DesignerProperty: Key: ExpandIconUrl, DisplayName: Expand Icon Url, FieldType: String, DefaultValue: ./assets/chevron-down-solid.svg, Description: Expand Icon Url
#DesignerProperty: Key: DragNDrop, DisplayName: Drag N Drop, FieldType: Boolean, DefaultValue: True, Description: Drag N Drop
#DesignerProperty: Key: HasCheckbox, DisplayName: Has Checkbox, FieldType: Boolean, DefaultValue: False, Description: Has Checkbox
#DesignerProperty: Key: IconHeight, DisplayName: Icon Height, FieldType: String, DefaultValue: 16px, Description: Icon Height
#DesignerProperty: Key: IconWidth, DisplayName: Icon Width, FieldType: String, DefaultValue: 16px, Description: Icon Width
#DesignerProperty: Key: InlineEdit, DisplayName: Inline Edit, FieldType: Boolean, DefaultValue: False, Description: Inline Edit
#DesignerProperty: Key: MultipleCheck, DisplayName: Multiple Check, FieldType: Boolean, DefaultValue: False, Description: Multiple Check
#DesignerProperty: Key: MultipleSelect, DisplayName: Multiple Select, FieldType: Boolean, DefaultValue: False, Description: Multiple Select
#DesignerProperty: Key: Size, DisplayName: Size, FieldType: String, DefaultValue: md, Description: Size, List: lg|md|none|sm|xl|xs
#DesignerProperty: Key: CheckBoxSize, DisplayName: Check Box Size, FieldType: String, DefaultValue: md, Description: Check Box Size, List: lg|md|none|sm|xl|xs
#DesignerProperty: Key: TextBoxSize, DisplayName: Text Box Size, FieldType: String, DefaultValue: sm, Description: Text Box Size, List: lg|md|none|sm|xl|xs
#DesignerProperty: Key: CheckBoxActiveColor, DisplayName: Check Box Active Color, FieldType: String, DefaultValue: , Description: Check Box Active Color
#DesignerProperty: Key: CheckBoxActiveBorderColor, DisplayName: Check Box Active Border Color, FieldType: String, DefaultValue: , Description: Check Box Active Border Color
#DesignerProperty: Key: Replace, DisplayName: Replace, FieldType: Boolean, DefaultValue: False, Description: Replace
#DesignerProperty: Key: UseLocalstorage, DisplayName: Use Localstorage, FieldType: Boolean, DefaultValue: True, Description: Use Localstorage
#DesignerProperty: Key: BackgroundColor, DisplayName: Background Color, FieldType: String, DefaultValue: , Description: Background Color
#DesignerProperty: Key: Rounded, DisplayName: Rounded, FieldType: String, DefaultValue: , Description: Rounded, List: none|rounded|2xl|3xl|full|lg|md|sm|xl|0
#DesignerProperty: Key: RoundedBox, DisplayName: Rounded Box, FieldType: Boolean, DefaultValue: False, Description: Rounded Box
#DesignerProperty: Key: Shadow, DisplayName: Shadow, FieldType: String, DefaultValue: none, Description: Shadow, List: 2xl|inner|lg|md|none|shadow|sm|xl
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: If visible.
#DesignerProperty: Key: Enabled, DisplayName: Enabled, FieldType: Boolean, DefaultValue: True, Description: If enabled.
#DesignerProperty: Key: PositionStyle, DisplayName: Position Style, FieldType: String, DefaultValue: none, Description: Position, List: absolute|fixed|none|relative|static|sticky
#DesignerProperty: Key: Position, DisplayName: Position Locations, FieldType: String, DefaultValue: t=?; b=?; r=?; l=?, Description: Position Locations
#DesignerProperty: Key: MarginAXYTBLR, DisplayName: Margins, FieldType: String, DefaultValue: a=?; x=?; y=?; t=?; b=?; l=?; r=? , Description: Margins A(all)-X(LR)-Y(TB)-T-B-L-R
#DesignerProperty: Key: PaddingAXYTBLR, DisplayName: Paddings, FieldType: String, DefaultValue: a=?; x=?; y=?; t=?; b=?; l=?; r=? , Description: Paddings A(all)-X(LR)-Y(TB)-T-B-L-R
#DesignerProperty: Key: RawClasses, DisplayName: Classes (;), FieldType: String, DefaultValue: , Description: Classes added to the HTML tag.
#DesignerProperty: Key: RawStyles, DisplayName: Styles (JSON), FieldType: String, DefaultValue: , Description: Styles added to the HTML tag. Must be a json String use = and ;
#DesignerProperty: Key: RawAttributes, DisplayName: Attributes (JSON), FieldType: String, DefaultValue: , Description: Attributes added to the HTML tag. Must be a json String use = and ;
'global variables in this module
Sub Class_Globals
    Public UI As UIShared 'ignore
    Public CustProps As Map 'ignore
    Private mCallBack As Object 'ignore
    Private mEventName As String 'ignore
    Private mElement As BANanoElement 'ignore
    Private mTarget As BANanoElement 'ignore
    Private mName As String 'ignore
    Private BANano As BANano   'ignore
    Private sPosition As String = "t=?; b=?; r=?; l=?"
    Private sPositionStyle As String = "none"
    Private sRawClasses As String = ""
    Private sRawStyles As String = ""
    Private sRawAttributes As String = ""
    Private sMarginAXYTBLR As String = "a=?; x=?; y=?; t=?; b=?; l=?; r=?"
    Private sPaddingAXYTBLR As String = "a=?; x=?; y=?; t=?; b=?; l=?; r=?"
    Private sParentID As String = ""
    Private bVisible As Boolean = True    'ignore
    Private bEnabled As Boolean = True    'ignore
    Public Tag As Object
    Private Options As Map
    Private sCollapseIconUrl As String = "./assets/chevron-right-solid.svg"
    Private bDragNDrop As Boolean = True
    Private sExpandIconUrl As String = "./assets/chevron-down-solid.svg"
    Private bHasCheckbox As Boolean = False
    Private sIconHeight As String = "16px"
    Private sIconWidth As String = "16px"
    Private bInlineEdit As Boolean = False
    Private bMultipleCheck As Boolean = False
    Private bMultipleSelect As Boolean = False
    Private sBackgroundColor As String = ""
    Private sRounded As String = ""
    Private bRoundedBox As Boolean = False
    Private sShadow As String = ""
    Private sSize As String = "md"
    Private sHeight As String = "full"
    Private sWidth As String = "200px"
    Private tv As BANanoObject
    Private bBuilt As Boolean
    Private sItemActiveColor As String = ""
    Private sItemColor As String = "primary"
    Private sItemFocusColor As String = ""
    Private sItemHoverColor As String = ""
    Private sCheckBoxActiveBorderColor As String = ""
    Private sCheckBoxActiveColor As String = ""
    Private sCheckBoxSize As String = ""
    Private bReplace As Boolean = False
    Private sTextBoxSize As String = ""
    Private bUseLocalstorage As Boolean = True
End Sub

#if css
.drag-over {
  border: 2px dashed #007bff;
}
#End If

'initialize the custom view class
Public Sub Initialize (Callback As Object, Name As String, EventName As String)
    If BANano.AssetsIsDefined("TreeView") = False Then
        BANano.Throw($"Uses Error: 'BANano.Await(app.UsesTreeView)' should be added for '${Name}'"$)
        Return
    End If
    UI.Initialize(Me)
    mElement = Null
    mEventName = UI.CleanID(EventName)
    mName = UI.CleanID(Name)
    mCallBack = Callback
    CustProps.Initialize
    Options.Initialize
    BANano.DependsOnAsset("daisyuitreeview.umd.js")
    bBuilt = False
End Sub
' returns the element id
Public Sub getID() As String
    Return mName
End Sub
'add this element to an existing parent element using current props
Public Sub AddComponent
    If sParentID = "" Then Return
    sParentID = UI.CleanID(sParentID)
    mTarget = BANano.GetElement("#" & sParentID)
    DesignerCreateView(mTarget, CustProps)
End Sub
'remove this element from the dom
Public Sub Remove()
    mElement.Remove
    BANano.SetMeToNull
End Sub
'set the parent id
Sub setParentID(s As String)
    s = UI.CleanID(s)
    sParentID = s
    CustProps.Put("ParentID", sParentID)
End Sub
'get the parent id
Sub getParentID As String
    Return sParentID
End Sub
'return the #ID of the element
Public Sub getHere() As String
    Return $"#${mName}"$
End Sub
'set Visible
Sub setVisible(b As Boolean)
    bVisible = b
    CustProps.Put("Visible", b)
    If mElement = Null Then Return
    UI.SetVisible(mElement, b)
End Sub
'get Visible
Sub getVisible As Boolean
    bVisible = UI.GetVisible(mElement)
    Return bVisible
End Sub
'set Enabled
Sub setEnabled(b As Boolean)
    bEnabled = b
    CustProps.Put("Enabled", b)
    If mElement = Null Then Return
    UI.SetEnabled(mElement, b)
End Sub
'get Enabled
Sub getEnabled As Boolean
    bEnabled = UI.GetEnabled(mElement)
    Return bEnabled
End Sub
'use to add an event to the element
Sub OnEvent(event As String, methodName As String)
    UI.OnEvent(mElement, event, mCallBack, methodName)
End Sub
'set Position Style
'options: static|relative|fixed|absolute|sticky|none
Sub setPositionStyle(s As String)
    sPositionStyle = s
    CustProps.put("PositionStyle", s)
    If mElement = Null Then Return
    If s <> "" Then UI.AddStyle(mElement, "position", s)
End Sub
Sub getPositionStyle As String
    Return sPositionStyle
End Sub
'set raw positions
Sub setPosition(s As String)
    sPosition = s
    CustProps.Put("Position", sPosition)
    If mElement = Null Then Return
    If s <> "" Then UI.SetPosition(mElement, sPosition)
End Sub
Sub getPosition As String
    Return sPosition
End Sub
Sub setAttributes(s As String)
    sRawAttributes = s
    CustProps.Put("RawAttributes", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetAttributes(mElement, sRawAttributes)
End Sub
'
Sub setStyles(s As String)
    sRawStyles = s
    CustProps.Put("RawStyles", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetStyles(mElement, sRawStyles)
End Sub
'
Sub setClasses(s As String)
    sRawClasses = s
    CustProps.put("RawClasses", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetClasses(mElement, sRawClasses)
End Sub
'
Sub setPaddingAXYTBLR(s As String)
    sPaddingAXYTBLR = s
    CustProps.Put("PaddingAXYTBLR", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetPaddingAXYTBLR(mElement, sPaddingAXYTBLR)
End Sub
'
Sub setMarginAXYTBLR(s As String)
    sMarginAXYTBLR = s
    CustProps.Put("MarginAXYTBLR", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetMarginAXYTBLR(mElement, sMarginAXYTBLR)
End Sub
'
Sub getAttributes As String
    Return sRawAttributes
End Sub
'
Sub getStyles As String
    Return sRawStyles
End Sub
'
Sub getClasses As String
    Return sRawClasses
End Sub
'
Sub getPaddingAXYTBLR As String
    Return sPaddingAXYTBLR
End Sub
'
Sub getMarginAXYTBLR As String
    Return sMarginAXYTBLR
End Sub

'code to design the view
Public Sub DesignerCreateView (Target As BANanoElement, Props As Map)
    mTarget = Target
    If Props <> Null Then
        CustProps = Props
        UI.SetProps(Props)
        UI.ExcludeBackgroundColor = True
        'UI.ExcludeTextColor = True
        'UI.ExcludeVisible = True
        'UI.ExcludeEnabled = True
        sCollapseIconUrl = Props.GetDefault("CollapseIconUrl", "./assets/chevron-right-solid.svg")
        sCollapseIconUrl = UI.CStr(sCollapseIconUrl)
        bDragNDrop = Props.GetDefault("DragNDrop", True)
        bDragNDrop = UI.CBool(bDragNDrop)
        sExpandIconUrl = Props.GetDefault("ExpandIconUrl", "./assets/chevron-down-solid.svg")
        sExpandIconUrl = UI.CStr(sExpandIconUrl)
        bHasCheckbox = Props.GetDefault("HasCheckbox", False)
        bHasCheckbox = UI.CBool(bHasCheckbox)
        sIconHeight = Props.GetDefault("IconHeight", "16px")
        sIconHeight = UI.CStr(sIconHeight)
        sIconWidth = Props.GetDefault("IconWidth", "16px")
        sIconWidth = UI.CStr(sIconWidth)
        bInlineEdit = Props.GetDefault("InlineEdit", False)
        bInlineEdit = UI.CBool(bInlineEdit)
        bMultipleCheck = Props.GetDefault("MultipleCheck", False)
        bMultipleCheck = UI.CBool(bMultipleCheck)
        bMultipleSelect = Props.GetDefault("MultipleSelect", False)
        bMultipleSelect = UI.CBool(bMultipleSelect)
        sBackgroundColor = Props.GetDefault("BackgroundColor", "")
        sBackgroundColor = UI.CStr(sBackgroundColor)
        sRounded = Props.GetDefault("Rounded", "")
        sRounded = UI.CStr(sRounded)
        bRoundedBox = Props.GetDefault("RoundedBox", False)
        bRoundedBox = UI.CBool(bRoundedBox)
        sShadow = Props.GetDefault("Shadow", "")
        sShadow = UI.CStr(sShadow)
        sSize = Props.GetDefault("Size", "md")
        sSize = UI.CStr(sSize)
        sHeight = Props.GetDefault("Height", "full")
        sHeight = UI.CStr(sHeight)
        sWidth = Props.GetDefault("Width", "200px")
        sWidth = UI.CStr(sWidth)
        sItemActiveColor = Props.GetDefault("ItemActiveColor", "")
        sItemActiveColor = UI.CStr(sItemActiveColor)
        sItemColor = Props.GetDefault("ItemColor", "primary")
        sItemColor = UI.CStr(sItemColor)
        sItemFocusColor = Props.GetDefault("ItemFocusColor", "")
        sItemFocusColor = UI.CStr(sItemFocusColor)
        sItemHoverColor = Props.GetDefault("ItemHoverColor", "")
        sItemHoverColor = UI.CStr(sItemHoverColor)
        sCheckBoxActiveBorderColor = Props.GetDefault("CheckBoxActiveBorderColor", "")
        sCheckBoxActiveBorderColor = UI.CStr(sCheckBoxActiveBorderColor)
        sCheckBoxActiveColor = Props.GetDefault("CheckBoxActiveColor", "")
        sCheckBoxActiveColor = UI.CStr(sCheckBoxActiveColor)
        sCheckBoxSize = Props.GetDefault("CheckBoxSize", "")
        sCheckBoxSize = UI.CStr(sCheckBoxSize)
        bReplace = Props.GetDefault("Replace", False)
        bReplace = UI.CBool(bReplace)
        sTextBoxSize = Props.GetDefault("TextBoxSize", "")
        sTextBoxSize = UI.CStr(sTextBoxSize)
        bUseLocalstorage = Props.GetDefault("UseLocalstorage", True)
        bUseLocalstorage = UI.CBool(bUseLocalstorage)
    End If
    '
    If sItemActiveColor <> "" Then
        If sItemActiveColor.StartsWith("active:") = False Then sItemActiveColor = UI.FixColor("active:bg", sItemActiveColor)
    End If
    If sItemFocusColor <> "" Then
        If sItemFocusColor.StartsWith("focus:") = False Then sItemFocusColor = UI.FixColor("focus:bg", sItemFocusColor)
    End If
    If sItemHoverColor <> "" Then
        If sItemHoverColor.StartsWith("hover:") = False Then sItemHoverColor = UI.FixColor("hover:bg", sItemHoverColor)
    End If
    
    Options.Initialize 
    Options.put("collapseIconUrl", sCollapseIconUrl)
    Options.put("dragNDrop", bDragNDrop)
    Options.put("expandIconUrl", sExpandIconUrl)
    Options.put("hasCheckbox", bHasCheckbox)
    Options.put("iconHeight", sIconHeight)
    Options.put("iconWidth", sIconWidth)
    Options.put("inlineEdit", bInlineEdit)
    Options.put("multipleCheck", bMultipleCheck)
    Options.put("multipleSelect", bMultipleSelect)
    Options.Put("itemColor", sItemColor)
    Options.Put("itemActiveColor", sItemActiveColor)
    Options.Put("itemFocusColor", sItemFocusColor)
    Options.Put("itemHoverColor", sItemHoverColor)
    Options.put("treeName", mName)
    Options.put("checkBoxActiveBorderColor", sCheckBoxActiveBorderColor)
    Options.put("checkBoxActiveColor", sCheckBoxActiveColor)
    Options.put("checkBoxSize", sCheckBoxSize)
    Options.put("replace", bReplace)
    Options.put("textBoxSize", sTextBoxSize)
    Options.put("useLocalstorage", bUseLocalstorage)
    '
    If sBackgroundColor <> "" Then UI.AddBackgroundColorDT(sBackgroundColor)
    If sSize <> "" Then UI.AddSizeDT("menu", sSize)
    If sRounded <> "" Then UI.AddRoundedDT(sRounded)
    If bRoundedBox Then UI.AddRoundedBoxDT
    If sShadow <> "" Then UI.AddShadowDT(sShadow)
    If sHeight <> "" Then UI.AddHeightDT(sHeight)
    If sWidth <> "" Then UI.AddWidthDT(sWidth)
    UI.AddClassDT("menu")
        
    Dim xattrs As String = UI.BuildExAttributes
    Dim xstyles As String = UI.BuildExStyle
    Dim xclasses As String = UI.BuildExClass
    If sParentID <> "" Then
        'does the parent exist
        If BANano.Exists($"#${sParentID}"$) = False Then
            BANano.Throw($"${mName}.DesignerCreateView: '${sParentID}' parent does not exist!"$)
            Return
        End If
        mTarget.Initialize($"#${sParentID}"$)
    End If
    mElement = mTarget.Append($"[BANCLEAN]<ul id="${mName}" class="${xclasses}" ${xattrs} style="${xstyles}"></ul>"$).Get("#" & mName)
    Dim e As BANanoEvent
    mElement.AddEventListener("nodeDrop", BANano.CallBack(mCallBack, $"${mName}_nodeDrop"$, Array(e)), True)
    mElement.AddEventListener("nodeDragStart", BANano.CallBack(mCallBack, $"${mName}_nodeDragStart"$, Array(e)), True)
    mElement.AddEventListener("nodeExpanded", BANano.CallBack(mCallBack, $"${mName}_nodeExpanded"$, Array(e)), True)
    mElement.AddEventListener("nodeCollapsed", BANano.CallBack(mCallBack, $"${mName}_nodeCollapsed"$, Array(e)), True)
    mElement.AddEventListener("nodeSelected", BANano.CallBack(mCallBack, $"${mName}_nodeSelected"$, Array(e)), True)
    mElement.AddEventListener("nodesSelected", BANano.CallBack(mCallBack, $"${mName}_nodesSelected"$, Array(e)), True)
    mElement.AddEventListener("nodeEdited", BANano.CallBack(mCallBack, $"${mName}_nodeEdited"$, Array(e)), True)
    mElement.AddEventListener("nodeChecked", BANano.CallBack(mCallBack, $"${mName}_nodeChecked"$, Array(e)), True)
    mElement.AddEventListener("nodeClick", BANano.CallBack(mCallBack, $"${mName}_nodeClick"$, Array(e)), True)
    mElement.AddEventListener("nodeEditCancelled", BANano.CallBack(mCallBack, $"${mName}_nodeEditCancelled"$, Array(e)), True)
    BuildTree
End Sub

private Sub BuildTree
    bBuilt = True
    Dim skey As String = $"#${mName}"$
    Dim el As BANanoElement = BANano.GetElement(skey).ToObject
    tv.Initialize2("DaisyUITreeView", Array(el, Options))
End Sub

Sub Refresh
    tv.RunMethod("refresh", Null)
End Sub

Sub collapseAll
    tv.RunMethod("collapseAll", Null)
End Sub

Sub expandAll
    tv.RunMethod("expandAll", Null)
End Sub

Sub nodeExists(nodeID As String) As Boolean
    Dim b As Boolean = tv.RunMethod("nodeExists", Array(nodeID)).Result
    Return b
End Sub

Sub findNode(nodeID As String) As Map
    Dim n As Object = tv.RunMethod("findNode", Array(nodeID)).Result
    Return n
End Sub

Sub addNode(parentId As String, nodeId As String, iconUrl As String, text As String, href As String)
    tv.RunMethod("addNode", Array(parentId, nodeId, iconUrl, text, href))
End Sub

Sub checkNode(nodeId As String, b As Boolean)
    tv.RunMethod("checkNode", Array(nodeId, b))
End Sub

Sub removeNode(nodeId As String)
    tv.RunMethod("removeNode", Array(nodeId))
End Sub

Sub expandNode(nodeId As String, state As Boolean)
    tv.RunMethod("expandNode", Array(nodeId, state))
End Sub

Sub enableNode(nodeId As String, state As Boolean)
    tv.RunMethod("enableNode", Array(nodeId, state))
End Sub

Sub Clear
    tv.RunMethod("clear", Null)
    If mElement <> Null Then mElement.empty
End Sub

Sub clearSelected
    tv.RunMethod("clearSelected", Null)
End Sub

Sub clearChecked
    tv.RunMethod("clearChecked", Null)
End Sub

Sub getChildren(nodeId As String) As List
    Dim l As Object = tv.RunMethod("getChildren", Array(nodeId)).Result
    Return l
End Sub

Sub removeChildren(nodeId As String)
    tv.RunMethod("removeChildren", Array(nodeId))
End Sub

Sub showNode(nodeId As String)
    tv.RunMethod("showNode", Array(nodeId))
End Sub

Sub hideNode(nodeId As String)
    tv.RunMethod("hideNode", Array(nodeId))
End Sub

Sub updateNode(nodeId As String, iconUrl As String, text As String, href As String)
    tv.RunMethod("updateNode", Array(nodeId, iconUrl, text, href))
End Sub

Sub selectNode(nodeId As String, state As Boolean)
    tv.RunMethod("selectNode", Array(nodeId, state))
End Sub

Sub selectNodes(nodes As List)
    tv.RunMethod("selectNodes", nodes)
End Sub

Sub getSelectedNode(includeChildren As Boolean) As Object
    Dim l As Object = tv.RunMethod("getSelectedNode", includeChildren).result
    Return l
End Sub

Sub getSelectedNodes As Object
    Dim l As Object = tv.RunMethod("getSelectedNodes", Null).Result
    Return l
End Sub

Sub getCheckedNodes As Object
    Dim l As Object = tv.RunMethod("getCheckedNodes", Null)
    Return l
End Sub

Sub checkNodes(nodes As List)
    tv.RunMethod("checkedNodes", nodes)
End Sub

'set Check Box Active Border Color
'options: primary|secondary|accent|neutral|info|success|warning|error|none
Sub setCheckBoxActiveBorderColor(s As String)
    sCheckBoxActiveBorderColor = s
    CustProps.put("CheckBoxActiveBorderColor", s)
    Options.Put("checkBoxActiveBorderColor", s)
    tv.GetField("settings").SetField("checkBoxActiveBorderColor", s)
End Sub
'set Check Box Active Color
'options: primary|secondary|accent|neutral|info|success|warning|error|none
Sub setCheckBoxActiveColor(s As String)
    sCheckBoxActiveColor = s
    CustProps.put("CheckBoxActiveColor", s)
    Options.Put("checkBoxActiveColor", s)
    tv.GetField("settings").SetField("checkBoxActiveColor", s)
End Sub
'set Check Box Size
'options: xs|none|sm|md|lg|xl
Sub setCheckBoxSize(s As String)
    sCheckBoxSize = s
    CustProps.put("CheckBoxSize", s)
    Options.Put("checkBoxSize", s)
    tv.GetField("settings").SetField("checkBoxSize", s)
End Sub
'set Replace
Sub setReplace(b As Boolean)
    bReplace = b
    CustProps.put("Replace", b)
    Options.put("replace", b)
    tv.GetField("settings").SetField("replace", b)
End Sub
'set Text Box Size
'options: xs|none|sm|md|lg|xl
Sub setTextBoxSize(s As String)
    sTextBoxSize = s
    CustProps.put("TextBoxSize", s)
    Options.Put("textBoxSize", s)
    tv.GetField("settings").SetField("textBoxSize", S)
End Sub
'set Use Localstorage
Sub setUseLocalstorage(b As Boolean)
    bUseLocalstorage = b
    CustProps.put("UseLocalstorage", b)
    Options.Put("useLocalstorage", b)
    tv.GetField("settings").SetField("useLocalstorage", b)
End Sub
'get Check Box Active Border Color
Sub getCheckBoxActiveBorderColor As String
    Return sCheckBoxActiveBorderColor
End Sub
'get Check Box Active Color
Sub getCheckBoxActiveColor As String
    Return sCheckBoxActiveColor
End Sub
'get Check Box Size
Sub getCheckBoxSize As String
    Return sCheckBoxSize
End Sub
'get Replace
Sub getReplace As Boolean
    Return bReplace
End Sub
'get Text Box Size
Sub getTextBoxSize As String
    Return sTextBoxSize
End Sub
'get Use Localstorage
Sub getUseLocalstorage As Boolean
    Return bUseLocalstorage
End Sub

'set Collapse Icon Url
Sub setCollapseIconUrl(s As String)
    sCollapseIconUrl = s
    CustProps.put("CollapseIconUrl", s)
    Options.put("collapseIconUrl", s)
    If bBuilt = False Then Return
    tv.GetField("settings").SetField("collapseIconUrl", s)
End Sub
'set Drag N Drop
Sub setDragNDrop(b As Boolean)
    bDragNDrop = b
    CustProps.put("DragNDrop", b)
    Options.put("dragNDrop", b)
    If bBuilt = False Then Return
    tv.GetField("settings").SetField("dragNDrop", b)
End Sub
'set Expand Icon Url
Sub setExpandIconUrl(s As String)
    sExpandIconUrl = s
    CustProps.put("ExpandIconUrl", s)
    Options.put("expandIconUrl", s)
    If bBuilt = False Then Return
    tv.GetField("settings").SetField("expandIconUrl", s)
End Sub
'set Has Checkbox
Sub setHasCheckbox(b As Boolean)
    bHasCheckbox = b
    CustProps.put("HasCheckbox", b)
    Options.put("hasCheckbox", b)
    If bBuilt = False Then Return
    tv.GetField("settings").SetField("hasCheckbox", b)
End Sub
'set Icon Height
Sub setIconHeight(s As String)
    sIconHeight = s
    CustProps.put("IconHeight", s)
    Options.put("iconHeight", s)
    If bBuilt = False Then Return
    tv.GetField("settings").SetField("iconHeight", s)
End Sub
'set Icon Width
Sub setIconWidth(s As String)
    sIconWidth = s
    CustProps.put("IconWidth", s)
    Options.put("iconWidth", s)
    If bBuilt = False Then Return
    tv.GetField("settings").SetField("iconWidth", s)
End Sub
'set Inline Edit
Sub setInlineEdit(b As Boolean)
    bInlineEdit = b
    CustProps.put("InlineEdit", b)
    Options.put("inlineEdit", b)
    If bBuilt = False Then Return
    tv.GetField("settings").SetField("inlineEdit", b)
End Sub
'set Multiple Check
Sub setMultipleCheck(b As Boolean)
    bMultipleCheck = b
    CustProps.put("MultipleCheck", b)
    Options.put("multipleCheck", b)
    If bBuilt = False Then Return
    tv.GetField("settings").SetField("multipleCheck", b)
End Sub
'set Multiple Select
Sub setMultipleSelect(b As Boolean)
    bMultipleSelect = b
    CustProps.put("MultipleSelect", b)
    Options.put("multipleSelect", b)
    If bBuilt = False Then Return
    tv.GetField("settings").SetField("multipleSelect", b)
End Sub
'get Collapse Icon Url
Sub getCollapseIconUrl As String
    Return sCollapseIconUrl
End Sub
'get Drag N Drop
Sub getDragNDrop As Boolean
    Return bDragNDrop
End Sub
'get Expand Icon Url
Sub getExpandIconUrl As String
    Return sExpandIconUrl
End Sub
'get Has Checkbox
Sub getHasCheckbox As Boolean
    Return bHasCheckbox
End Sub
'get Icon Height
Sub getIconHeight As String
    Return sIconHeight
End Sub
'get Icon Width
Sub getIconWidth As String
    Return sIconWidth
End Sub
'get Inline Edit
Sub getInlineEdit As Boolean
    Return bInlineEdit
End Sub
'get Multiple Check
Sub getMultipleCheck As Boolean
    Return bMultipleCheck
End Sub
'get Multiple Select
Sub getMultipleSelect As Boolean
    Return bMultipleSelect
End Sub

'set Background Color
Sub setBackgroundColor(s As String)
    sBackgroundColor = s
    CustProps.put("BackgroundColor", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetBackgroundColor(mElement, sBackgroundColor)
End Sub
'set Height
Sub setHeight(s As String)
    sHeight = s
    CustProps.put("Height", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetHeight(mElement, sHeight)
End Sub
'set Rounded
'options: none|rounded|2xl|3xl|full|lg|md|sm|xl|0
Sub setRounded(s As String)
    sRounded = s
    CustProps.put("Rounded", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetRounded(mElement, sRounded)
End Sub
'set Rounded Box
Sub setRoundedBox(b As Boolean)
    bRoundedBox = b
    CustProps.put("RoundedBox", b)
    If mElement = Null Then Return
    UI.SetRoundedBox(mElement, bRoundedBox)
End Sub

'set Shadow
'options: shadow|sm|md|lg|xl|2xl|inner|none
Sub setShadow(s As String)
    sShadow = s
    CustProps.put("Shadow", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetShadow(mElement, sShadow)
End Sub
'set Size
'options: lg|md|none|sm|xl|xs
Sub setSize(s As String)
    sSize = s
    CustProps.put("Size", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetSize(mElement, "size", "menu", sSize)
End Sub
'set Width
Sub setWidth(s As String)
    sWidth = s
    CustProps.put("Width", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetWidth(mElement, sWidth)
End Sub
'get Background Color
Sub getBackgroundColor As String
    Return sBackgroundColor
End Sub
'get Height
Sub getHeight As String
    Return sHeight
End Sub
'get Rounded
Sub getRounded As String
    Return sRounded
End Sub
'get Rounded Box
Sub getRoundedBox As Boolean
    Return bRoundedBox
End Sub

'get Shadow
Sub getShadow As String
    Return sShadow
End Sub
'get Size
Sub getSize As String
    Return sSize
End Sub
'get Width
Sub getWidth As String
    Return sWidth
End Sub
 

Mashiane

Expert
Licensed User
Longtime User
So SithasoDaisy5 has a new TreeView component, which was almost all done via AI.

Adopted to B4x, the complete source code of the custom view is..

B4X:
#IgnoreWarnings:12
#Event: nodeDrop (e As BANanoEvent)
#Event: nodeDragStart (e As BANanoEvent)
#Event: nodeExpanded (e As BANanoEvent)
#Event: nodeCollapsed (e As BANanoEvent)
#Event: nodeEdited (e As BANanoEvent)
#Event: nodeSelected (e As BANanoEvent)
#Event: nodesSelected (e As BANanoEvent)
#Event: nodesChecked (e As BANanoEvent)
#Event: nodeClick (e As BANanoEvent)
#Event: nodeEditCancelled (e As BANanoEvent)

#DesignerProperty: Key: ParentID, DisplayName: ParentID, FieldType: String, DefaultValue: , Description: The ParentID of this component
#DesignerProperty: Key: ItemColor, DisplayName: Item Color, FieldType: String, DefaultValue: primary, Description: Item Color
#DesignerProperty: Key: ItemActiveColor, DisplayName: Item Active Color, FieldType: String, DefaultValue: , Description: Item Active Color
#DesignerProperty: Key: ItemFocusColor, DisplayName: Item Focus Color, FieldType: String, DefaultValue: , Description: Item Focus Color
#DesignerProperty: Key: ItemHoverColor, DisplayName: Item Hover Color, FieldType: String, DefaultValue: , Description: Item Hover Color
#DesignerProperty: Key: Height, DisplayName: Height, FieldType: String, DefaultValue: full, Description: Height
#DesignerProperty: Key: Width, DisplayName: Width, FieldType: String, DefaultValue: 200px, Description: Width
#DesignerProperty: Key: CollapseIconUrl, DisplayName: Collapse Icon Url, FieldType: String, DefaultValue: ./assets/chevron-right-solid.svg, Description: Collapse Icon Url
#DesignerProperty: Key: ExpandIconUrl, DisplayName: Expand Icon Url, FieldType: String, DefaultValue: ./assets/chevron-down-solid.svg, Description: Expand Icon Url
#DesignerProperty: Key: DragNDrop, DisplayName: Drag N Drop, FieldType: Boolean, DefaultValue: True, Description: Drag N Drop
#DesignerProperty: Key: HasCheckbox, DisplayName: Has Checkbox, FieldType: Boolean, DefaultValue: False, Description: Has Checkbox
#DesignerProperty: Key: IconHeight, DisplayName: Icon Height, FieldType: String, DefaultValue: 16px, Description: Icon Height
#DesignerProperty: Key: IconWidth, DisplayName: Icon Width, FieldType: String, DefaultValue: 16px, Description: Icon Width
#DesignerProperty: Key: InlineEdit, DisplayName: Inline Edit, FieldType: Boolean, DefaultValue: False, Description: Inline Edit
#DesignerProperty: Key: MultipleCheck, DisplayName: Multiple Check, FieldType: Boolean, DefaultValue: False, Description: Multiple Check
#DesignerProperty: Key: MultipleSelect, DisplayName: Multiple Select, FieldType: Boolean, DefaultValue: False, Description: Multiple Select
#DesignerProperty: Key: Size, DisplayName: Size, FieldType: String, DefaultValue: md, Description: Size, List: lg|md|none|sm|xl|xs
#DesignerProperty: Key: CheckBoxSize, DisplayName: Check Box Size, FieldType: String, DefaultValue: md, Description: Check Box Size, List: lg|md|none|sm|xl|xs
#DesignerProperty: Key: TextBoxSize, DisplayName: Text Box Size, FieldType: String, DefaultValue: sm, Description: Text Box Size, List: lg|md|none|sm|xl|xs
#DesignerProperty: Key: CheckBoxActiveColor, DisplayName: Check Box Active Color, FieldType: String, DefaultValue: , Description: Check Box Active Color
#DesignerProperty: Key: CheckBoxActiveBorderColor, DisplayName: Check Box Active Border Color, FieldType: String, DefaultValue: , Description: Check Box Active Border Color
#DesignerProperty: Key: Replace, DisplayName: Replace, FieldType: Boolean, DefaultValue: False, Description: Replace
#DesignerProperty: Key: UseLocalstorage, DisplayName: Use Localstorage, FieldType: Boolean, DefaultValue: True, Description: Use Localstorage
#DesignerProperty: Key: BackgroundColor, DisplayName: Background Color, FieldType: String, DefaultValue: , Description: Background Color
#DesignerProperty: Key: Rounded, DisplayName: Rounded, FieldType: String, DefaultValue: , Description: Rounded, List: none|rounded|2xl|3xl|full|lg|md|sm|xl|0
#DesignerProperty: Key: RoundedBox, DisplayName: Rounded Box, FieldType: Boolean, DefaultValue: False, Description: Rounded Box
#DesignerProperty: Key: Shadow, DisplayName: Shadow, FieldType: String, DefaultValue: none, Description: Shadow, List: 2xl|inner|lg|md|none|shadow|sm|xl
#DesignerProperty: Key: Visible, DisplayName: Visible, FieldType: Boolean, DefaultValue: True, Description: If visible.
#DesignerProperty: Key: Enabled, DisplayName: Enabled, FieldType: Boolean, DefaultValue: True, Description: If enabled.
#DesignerProperty: Key: PositionStyle, DisplayName: Position Style, FieldType: String, DefaultValue: none, Description: Position, List: absolute|fixed|none|relative|static|sticky
#DesignerProperty: Key: Position, DisplayName: Position Locations, FieldType: String, DefaultValue: t=?; b=?; r=?; l=?, Description: Position Locations
#DesignerProperty: Key: MarginAXYTBLR, DisplayName: Margins, FieldType: String, DefaultValue: a=?; x=?; y=?; t=?; b=?; l=?; r=? , Description: Margins A(all)-X(LR)-Y(TB)-T-B-L-R
#DesignerProperty: Key: PaddingAXYTBLR, DisplayName: Paddings, FieldType: String, DefaultValue: a=?; x=?; y=?; t=?; b=?; l=?; r=? , Description: Paddings A(all)-X(LR)-Y(TB)-T-B-L-R
#DesignerProperty: Key: RawClasses, DisplayName: Classes (;), FieldType: String, DefaultValue: , Description: Classes added to the HTML tag.
#DesignerProperty: Key: RawStyles, DisplayName: Styles (JSON), FieldType: String, DefaultValue: , Description: Styles added to the HTML tag. Must be a json String use = and ;
#DesignerProperty: Key: RawAttributes, DisplayName: Attributes (JSON), FieldType: String, DefaultValue: , Description: Attributes added to the HTML tag. Must be a json String use = and ;
'global variables in this module
Sub Class_Globals
    Public UI As UIShared 'ignore
    Public CustProps As Map 'ignore
    Private mCallBack As Object 'ignore
    Private mEventName As String 'ignore
    Private mElement As BANanoElement 'ignore
    Private mTarget As BANanoElement 'ignore
    Private mName As String 'ignore
    Private BANano As BANano   'ignore
    Private sPosition As String = "t=?; b=?; r=?; l=?"
    Private sPositionStyle As String = "none"
    Private sRawClasses As String = ""
    Private sRawStyles As String = ""
    Private sRawAttributes As String = ""
    Private sMarginAXYTBLR As String = "a=?; x=?; y=?; t=?; b=?; l=?; r=?"
    Private sPaddingAXYTBLR As String = "a=?; x=?; y=?; t=?; b=?; l=?; r=?"
    Private sParentID As String = ""
    Private bVisible As Boolean = True    'ignore
    Private bEnabled As Boolean = True    'ignore
    Public Tag As Object
    Private Options As Map
    Private sCollapseIconUrl As String = "./assets/chevron-right-solid.svg"
    Private bDragNDrop As Boolean = True
    Private sExpandIconUrl As String = "./assets/chevron-down-solid.svg"
    Private bHasCheckbox As Boolean = False
    Private sIconHeight As String = "16px"
    Private sIconWidth As String = "16px"
    Private bInlineEdit As Boolean = False
    Private bMultipleCheck As Boolean = False
    Private bMultipleSelect As Boolean = False
    Private sBackgroundColor As String = ""
    Private sRounded As String = ""
    Private bRoundedBox As Boolean = False
    Private sShadow As String = ""
    Private sSize As String = "md"
    Private sHeight As String = "full"
    Private sWidth As String = "200px"
    Private tv As BANanoObject
    Private bBuilt As Boolean
    Private sItemActiveColor As String = ""
    Private sItemColor As String = "primary"
    Private sItemFocusColor As String = ""
    Private sItemHoverColor As String = ""
    Private sCheckBoxActiveBorderColor As String = ""
    Private sCheckBoxActiveColor As String = ""
    Private sCheckBoxSize As String = ""
    Private bReplace As Boolean = False
    Private sTextBoxSize As String = ""
    Private bUseLocalstorage As Boolean = True
End Sub

#if css
.drag-over {
  border: 2px dashed #007bff;
}
#End If

'initialize the custom view class
Public Sub Initialize (Callback As Object, Name As String, EventName As String)
    If BANano.AssetsIsDefined("TreeView") = False Then
        BANano.Throw($"Uses Error: 'BANano.Await(app.UsesTreeView)' should be added for '${Name}'"$)
        Return
    End If
    UI.Initialize(Me)
    mElement = Null
    mEventName = UI.CleanID(EventName)
    mName = UI.CleanID(Name)
    mCallBack = Callback
    CustProps.Initialize
    Options.Initialize
    BANano.DependsOnAsset("daisyuitreeview.umd.js")
    bBuilt = False
End Sub
' returns the element id
Public Sub getID() As String
    Return mName
End Sub
'add this element to an existing parent element using current props
Public Sub AddComponent
    If sParentID = "" Then Return
    sParentID = UI.CleanID(sParentID)
    mTarget = BANano.GetElement("#" & sParentID)
    DesignerCreateView(mTarget, CustProps)
End Sub
'remove this element from the dom
Public Sub Remove()
    mElement.Remove
    BANano.SetMeToNull
End Sub
'set the parent id
Sub setParentID(s As String)
    s = UI.CleanID(s)
    sParentID = s
    CustProps.Put("ParentID", sParentID)
End Sub
'get the parent id
Sub getParentID As String
    Return sParentID
End Sub
'return the #ID of the element
Public Sub getHere() As String
    Return $"#${mName}"$
End Sub
'set Visible
Sub setVisible(b As Boolean)
    bVisible = b
    CustProps.Put("Visible", b)
    If mElement = Null Then Return
    UI.SetVisible(mElement, b)
End Sub
'get Visible
Sub getVisible As Boolean
    bVisible = UI.GetVisible(mElement)
    Return bVisible
End Sub
'set Enabled
Sub setEnabled(b As Boolean)
    bEnabled = b
    CustProps.Put("Enabled", b)
    If mElement = Null Then Return
    UI.SetEnabled(mElement, b)
End Sub
'get Enabled
Sub getEnabled As Boolean
    bEnabled = UI.GetEnabled(mElement)
    Return bEnabled
End Sub
'use to add an event to the element
Sub OnEvent(event As String, methodName As String)
    UI.OnEvent(mElement, event, mCallBack, methodName)
End Sub
'set Position Style
'options: static|relative|fixed|absolute|sticky|none
Sub setPositionStyle(s As String)
    sPositionStyle = s
    CustProps.put("PositionStyle", s)
    If mElement = Null Then Return
    If s <> "" Then UI.AddStyle(mElement, "position", s)
End Sub
Sub getPositionStyle As String
    Return sPositionStyle
End Sub
'set raw positions
Sub setPosition(s As String)
    sPosition = s
    CustProps.Put("Position", sPosition)
    If mElement = Null Then Return
    If s <> "" Then UI.SetPosition(mElement, sPosition)
End Sub
Sub getPosition As String
    Return sPosition
End Sub
Sub setAttributes(s As String)
    sRawAttributes = s
    CustProps.Put("RawAttributes", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetAttributes(mElement, sRawAttributes)
End Sub
'
Sub setStyles(s As String)
    sRawStyles = s
    CustProps.Put("RawStyles", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetStyles(mElement, sRawStyles)
End Sub
'
Sub setClasses(s As String)
    sRawClasses = s
    CustProps.put("RawClasses", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetClasses(mElement, sRawClasses)
End Sub
'
Sub setPaddingAXYTBLR(s As String)
    sPaddingAXYTBLR = s
    CustProps.Put("PaddingAXYTBLR", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetPaddingAXYTBLR(mElement, sPaddingAXYTBLR)
End Sub
'
Sub setMarginAXYTBLR(s As String)
    sMarginAXYTBLR = s
    CustProps.Put("MarginAXYTBLR", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetMarginAXYTBLR(mElement, sMarginAXYTBLR)
End Sub
'
Sub getAttributes As String
    Return sRawAttributes
End Sub
'
Sub getStyles As String
    Return sRawStyles
End Sub
'
Sub getClasses As String
    Return sRawClasses
End Sub
'
Sub getPaddingAXYTBLR As String
    Return sPaddingAXYTBLR
End Sub
'
Sub getMarginAXYTBLR As String
    Return sMarginAXYTBLR
End Sub

'code to design the view
Public Sub DesignerCreateView (Target As BANanoElement, Props As Map)
    mTarget = Target
    If Props <> Null Then
        CustProps = Props
        UI.SetProps(Props)
        UI.ExcludeBackgroundColor = True
        'UI.ExcludeTextColor = True
        'UI.ExcludeVisible = True
        'UI.ExcludeEnabled = True
        sCollapseIconUrl = Props.GetDefault("CollapseIconUrl", "./assets/chevron-right-solid.svg")
        sCollapseIconUrl = UI.CStr(sCollapseIconUrl)
        bDragNDrop = Props.GetDefault("DragNDrop", True)
        bDragNDrop = UI.CBool(bDragNDrop)
        sExpandIconUrl = Props.GetDefault("ExpandIconUrl", "./assets/chevron-down-solid.svg")
        sExpandIconUrl = UI.CStr(sExpandIconUrl)
        bHasCheckbox = Props.GetDefault("HasCheckbox", False)
        bHasCheckbox = UI.CBool(bHasCheckbox)
        sIconHeight = Props.GetDefault("IconHeight", "16px")
        sIconHeight = UI.CStr(sIconHeight)
        sIconWidth = Props.GetDefault("IconWidth", "16px")
        sIconWidth = UI.CStr(sIconWidth)
        bInlineEdit = Props.GetDefault("InlineEdit", False)
        bInlineEdit = UI.CBool(bInlineEdit)
        bMultipleCheck = Props.GetDefault("MultipleCheck", False)
        bMultipleCheck = UI.CBool(bMultipleCheck)
        bMultipleSelect = Props.GetDefault("MultipleSelect", False)
        bMultipleSelect = UI.CBool(bMultipleSelect)
        sBackgroundColor = Props.GetDefault("BackgroundColor", "")
        sBackgroundColor = UI.CStr(sBackgroundColor)
        sRounded = Props.GetDefault("Rounded", "")
        sRounded = UI.CStr(sRounded)
        bRoundedBox = Props.GetDefault("RoundedBox", False)
        bRoundedBox = UI.CBool(bRoundedBox)
        sShadow = Props.GetDefault("Shadow", "")
        sShadow = UI.CStr(sShadow)
        sSize = Props.GetDefault("Size", "md")
        sSize = UI.CStr(sSize)
        sHeight = Props.GetDefault("Height", "full")
        sHeight = UI.CStr(sHeight)
        sWidth = Props.GetDefault("Width", "200px")
        sWidth = UI.CStr(sWidth)
        sItemActiveColor = Props.GetDefault("ItemActiveColor", "")
        sItemActiveColor = UI.CStr(sItemActiveColor)
        sItemColor = Props.GetDefault("ItemColor", "primary")
        sItemColor = UI.CStr(sItemColor)
        sItemFocusColor = Props.GetDefault("ItemFocusColor", "")
        sItemFocusColor = UI.CStr(sItemFocusColor)
        sItemHoverColor = Props.GetDefault("ItemHoverColor", "")
        sItemHoverColor = UI.CStr(sItemHoverColor)
        sCheckBoxActiveBorderColor = Props.GetDefault("CheckBoxActiveBorderColor", "")
        sCheckBoxActiveBorderColor = UI.CStr(sCheckBoxActiveBorderColor)
        sCheckBoxActiveColor = Props.GetDefault("CheckBoxActiveColor", "")
        sCheckBoxActiveColor = UI.CStr(sCheckBoxActiveColor)
        sCheckBoxSize = Props.GetDefault("CheckBoxSize", "")
        sCheckBoxSize = UI.CStr(sCheckBoxSize)
        bReplace = Props.GetDefault("Replace", False)
        bReplace = UI.CBool(bReplace)
        sTextBoxSize = Props.GetDefault("TextBoxSize", "")
        sTextBoxSize = UI.CStr(sTextBoxSize)
        bUseLocalstorage = Props.GetDefault("UseLocalstorage", True)
        bUseLocalstorage = UI.CBool(bUseLocalstorage)
    End If
    '
    If sItemActiveColor <> "" Then
        If sItemActiveColor.StartsWith("active:") = False Then sItemActiveColor = UI.FixColor("active:bg", sItemActiveColor)
    End If
    If sItemFocusColor <> "" Then
        If sItemFocusColor.StartsWith("focus:") = False Then sItemFocusColor = UI.FixColor("focus:bg", sItemFocusColor)
    End If
    If sItemHoverColor <> "" Then
        If sItemHoverColor.StartsWith("hover:") = False Then sItemHoverColor = UI.FixColor("hover:bg", sItemHoverColor)
    End If
   
    Options.Initialize
    Options.put("collapseIconUrl", sCollapseIconUrl)
    Options.put("dragNDrop", bDragNDrop)
    Options.put("expandIconUrl", sExpandIconUrl)
    Options.put("hasCheckbox", bHasCheckbox)
    Options.put("iconHeight", sIconHeight)
    Options.put("iconWidth", sIconWidth)
    Options.put("inlineEdit", bInlineEdit)
    Options.put("multipleCheck", bMultipleCheck)
    Options.put("multipleSelect", bMultipleSelect)
    Options.Put("itemColor", sItemColor)
    Options.Put("itemActiveColor", sItemActiveColor)
    Options.Put("itemFocusColor", sItemFocusColor)
    Options.Put("itemHoverColor", sItemHoverColor)
    Options.put("treeName", mName)
    Options.put("checkBoxActiveBorderColor", sCheckBoxActiveBorderColor)
    Options.put("checkBoxActiveColor", sCheckBoxActiveColor)
    Options.put("checkBoxSize", sCheckBoxSize)
    Options.put("replace", bReplace)
    Options.put("textBoxSize", sTextBoxSize)
    Options.put("useLocalstorage", bUseLocalstorage)
    '
    If sBackgroundColor <> "" Then UI.AddBackgroundColorDT(sBackgroundColor)
    If sSize <> "" Then UI.AddSizeDT("menu", sSize)
    If sRounded <> "" Then UI.AddRoundedDT(sRounded)
    If bRoundedBox Then UI.AddRoundedBoxDT
    If sShadow <> "" Then UI.AddShadowDT(sShadow)
    If sHeight <> "" Then UI.AddHeightDT(sHeight)
    If sWidth <> "" Then UI.AddWidthDT(sWidth)
    UI.AddClassDT("menu")
       
    Dim xattrs As String = UI.BuildExAttributes
    Dim xstyles As String = UI.BuildExStyle
    Dim xclasses As String = UI.BuildExClass
    If sParentID <> "" Then
        'does the parent exist
        If BANano.Exists($"#${sParentID}"$) = False Then
            BANano.Throw($"${mName}.DesignerCreateView: '${sParentID}' parent does not exist!"$)
            Return
        End If
        mTarget.Initialize($"#${sParentID}"$)
    End If
    mElement = mTarget.Append($"[BANCLEAN]<ul id="${mName}" class="${xclasses}" ${xattrs} style="${xstyles}"></ul>"$).Get("#" & mName)
    Dim e As BANanoEvent
    mElement.AddEventListener("nodeDrop", BANano.CallBack(mCallBack, $"${mName}_nodeDrop"$, Array(e)), True)
    mElement.AddEventListener("nodeDragStart", BANano.CallBack(mCallBack, $"${mName}_nodeDragStart"$, Array(e)), True)
    mElement.AddEventListener("nodeExpanded", BANano.CallBack(mCallBack, $"${mName}_nodeExpanded"$, Array(e)), True)
    mElement.AddEventListener("nodeCollapsed", BANano.CallBack(mCallBack, $"${mName}_nodeCollapsed"$, Array(e)), True)
    mElement.AddEventListener("nodeSelected", BANano.CallBack(mCallBack, $"${mName}_nodeSelected"$, Array(e)), True)
    mElement.AddEventListener("nodesSelected", BANano.CallBack(mCallBack, $"${mName}_nodesSelected"$, Array(e)), True)
    mElement.AddEventListener("nodeEdited", BANano.CallBack(mCallBack, $"${mName}_nodeEdited"$, Array(e)), True)
    mElement.AddEventListener("nodeChecked", BANano.CallBack(mCallBack, $"${mName}_nodeChecked"$, Array(e)), True)
    mElement.AddEventListener("nodeClick", BANano.CallBack(mCallBack, $"${mName}_nodeClick"$, Array(e)), True)
    mElement.AddEventListener("nodeEditCancelled", BANano.CallBack(mCallBack, $"${mName}_nodeEditCancelled"$, Array(e)), True)
    BuildTree
End Sub

private Sub BuildTree
    bBuilt = True
    Dim skey As String = $"#${mName}"$
    Dim el As BANanoElement = BANano.GetElement(skey).ToObject
    tv.Initialize2("DaisyUITreeView", Array(el, Options))
End Sub

Sub Refresh
    tv.RunMethod("refresh", Null)
End Sub

Sub collapseAll
    tv.RunMethod("collapseAll", Null)
End Sub

Sub expandAll
    tv.RunMethod("expandAll", Null)
End Sub

Sub nodeExists(nodeID As String) As Boolean
    Dim b As Boolean = tv.RunMethod("nodeExists", Array(nodeID)).Result
    Return b
End Sub

Sub findNode(nodeID As String) As Map
    Dim n As Object = tv.RunMethod("findNode", Array(nodeID)).Result
    Return n
End Sub

Sub addNode(parentId As String, nodeId As String, iconUrl As String, text As String, href As String)
    tv.RunMethod("addNode", Array(parentId, nodeId, iconUrl, text, href))
End Sub

Sub checkNode(nodeId As String, b As Boolean)
    tv.RunMethod("checkNode", Array(nodeId, b))
End Sub

Sub removeNode(nodeId As String)
    tv.RunMethod("removeNode", Array(nodeId))
End Sub

Sub expandNode(nodeId As String, state As Boolean)
    tv.RunMethod("expandNode", Array(nodeId, state))
End Sub

Sub enableNode(nodeId As String, state As Boolean)
    tv.RunMethod("enableNode", Array(nodeId, state))
End Sub

Sub Clear
    tv.RunMethod("clear", Null)
    If mElement <> Null Then mElement.empty
End Sub

Sub clearSelected
    tv.RunMethod("clearSelected", Null)
End Sub

Sub clearChecked
    tv.RunMethod("clearChecked", Null)
End Sub

Sub getChildren(nodeId As String) As List
    Dim l As Object = tv.RunMethod("getChildren", Array(nodeId)).Result
    Return l
End Sub

Sub removeChildren(nodeId As String)
    tv.RunMethod("removeChildren", Array(nodeId))
End Sub

Sub showNode(nodeId As String)
    tv.RunMethod("showNode", Array(nodeId))
End Sub

Sub hideNode(nodeId As String)
    tv.RunMethod("hideNode", Array(nodeId))
End Sub

Sub updateNode(nodeId As String, iconUrl As String, text As String, href As String)
    tv.RunMethod("updateNode", Array(nodeId, iconUrl, text, href))
End Sub

Sub selectNode(nodeId As String, state As Boolean)
    tv.RunMethod("selectNode", Array(nodeId, state))
End Sub

Sub selectNodes(nodes As List)
    tv.RunMethod("selectNodes", nodes)
End Sub

Sub getSelectedNode(includeChildren As Boolean) As Object
    Dim l As Object = tv.RunMethod("getSelectedNode", includeChildren).result
    Return l
End Sub

Sub getSelectedNodes As Object
    Dim l As Object = tv.RunMethod("getSelectedNodes", Null).Result
    Return l
End Sub

Sub getCheckedNodes As Object
    Dim l As Object = tv.RunMethod("getCheckedNodes", Null)
    Return l
End Sub

Sub checkNodes(nodes As List)
    tv.RunMethod("checkedNodes", nodes)
End Sub

'set Check Box Active Border Color
'options: primary|secondary|accent|neutral|info|success|warning|error|none
Sub setCheckBoxActiveBorderColor(s As String)
    sCheckBoxActiveBorderColor = s
    CustProps.put("CheckBoxActiveBorderColor", s)
    Options.Put("checkBoxActiveBorderColor", s)
    tv.GetField("settings").SetField("checkBoxActiveBorderColor", s)
End Sub
'set Check Box Active Color
'options: primary|secondary|accent|neutral|info|success|warning|error|none
Sub setCheckBoxActiveColor(s As String)
    sCheckBoxActiveColor = s
    CustProps.put("CheckBoxActiveColor", s)
    Options.Put("checkBoxActiveColor", s)
    tv.GetField("settings").SetField("checkBoxActiveColor", s)
End Sub
'set Check Box Size
'options: xs|none|sm|md|lg|xl
Sub setCheckBoxSize(s As String)
    sCheckBoxSize = s
    CustProps.put("CheckBoxSize", s)
    Options.Put("checkBoxSize", s)
    tv.GetField("settings").SetField("checkBoxSize", s)
End Sub
'set Replace
Sub setReplace(b As Boolean)
    bReplace = b
    CustProps.put("Replace", b)
    Options.put("replace", b)
    tv.GetField("settings").SetField("replace", b)
End Sub
'set Text Box Size
'options: xs|none|sm|md|lg|xl
Sub setTextBoxSize(s As String)
    sTextBoxSize = s
    CustProps.put("TextBoxSize", s)
    Options.Put("textBoxSize", s)
    tv.GetField("settings").SetField("textBoxSize", S)
End Sub
'set Use Localstorage
Sub setUseLocalstorage(b As Boolean)
    bUseLocalstorage = b
    CustProps.put("UseLocalstorage", b)
    Options.Put("useLocalstorage", b)
    tv.GetField("settings").SetField("useLocalstorage", b)
End Sub
'get Check Box Active Border Color
Sub getCheckBoxActiveBorderColor As String
    Return sCheckBoxActiveBorderColor
End Sub
'get Check Box Active Color
Sub getCheckBoxActiveColor As String
    Return sCheckBoxActiveColor
End Sub
'get Check Box Size
Sub getCheckBoxSize As String
    Return sCheckBoxSize
End Sub
'get Replace
Sub getReplace As Boolean
    Return bReplace
End Sub
'get Text Box Size
Sub getTextBoxSize As String
    Return sTextBoxSize
End Sub
'get Use Localstorage
Sub getUseLocalstorage As Boolean
    Return bUseLocalstorage
End Sub

'set Collapse Icon Url
Sub setCollapseIconUrl(s As String)
    sCollapseIconUrl = s
    CustProps.put("CollapseIconUrl", s)
    Options.put("collapseIconUrl", s)
    If bBuilt = False Then Return
    tv.GetField("settings").SetField("collapseIconUrl", s)
End Sub
'set Drag N Drop
Sub setDragNDrop(b As Boolean)
    bDragNDrop = b
    CustProps.put("DragNDrop", b)
    Options.put("dragNDrop", b)
    If bBuilt = False Then Return
    tv.GetField("settings").SetField("dragNDrop", b)
End Sub
'set Expand Icon Url
Sub setExpandIconUrl(s As String)
    sExpandIconUrl = s
    CustProps.put("ExpandIconUrl", s)
    Options.put("expandIconUrl", s)
    If bBuilt = False Then Return
    tv.GetField("settings").SetField("expandIconUrl", s)
End Sub
'set Has Checkbox
Sub setHasCheckbox(b As Boolean)
    bHasCheckbox = b
    CustProps.put("HasCheckbox", b)
    Options.put("hasCheckbox", b)
    If bBuilt = False Then Return
    tv.GetField("settings").SetField("hasCheckbox", b)
End Sub
'set Icon Height
Sub setIconHeight(s As String)
    sIconHeight = s
    CustProps.put("IconHeight", s)
    Options.put("iconHeight", s)
    If bBuilt = False Then Return
    tv.GetField("settings").SetField("iconHeight", s)
End Sub
'set Icon Width
Sub setIconWidth(s As String)
    sIconWidth = s
    CustProps.put("IconWidth", s)
    Options.put("iconWidth", s)
    If bBuilt = False Then Return
    tv.GetField("settings").SetField("iconWidth", s)
End Sub
'set Inline Edit
Sub setInlineEdit(b As Boolean)
    bInlineEdit = b
    CustProps.put("InlineEdit", b)
    Options.put("inlineEdit", b)
    If bBuilt = False Then Return
    tv.GetField("settings").SetField("inlineEdit", b)
End Sub
'set Multiple Check
Sub setMultipleCheck(b As Boolean)
    bMultipleCheck = b
    CustProps.put("MultipleCheck", b)
    Options.put("multipleCheck", b)
    If bBuilt = False Then Return
    tv.GetField("settings").SetField("multipleCheck", b)
End Sub
'set Multiple Select
Sub setMultipleSelect(b As Boolean)
    bMultipleSelect = b
    CustProps.put("MultipleSelect", b)
    Options.put("multipleSelect", b)
    If bBuilt = False Then Return
    tv.GetField("settings").SetField("multipleSelect", b)
End Sub
'get Collapse Icon Url
Sub getCollapseIconUrl As String
    Return sCollapseIconUrl
End Sub
'get Drag N Drop
Sub getDragNDrop As Boolean
    Return bDragNDrop
End Sub
'get Expand Icon Url
Sub getExpandIconUrl As String
    Return sExpandIconUrl
End Sub
'get Has Checkbox
Sub getHasCheckbox As Boolean
    Return bHasCheckbox
End Sub
'get Icon Height
Sub getIconHeight As String
    Return sIconHeight
End Sub
'get Icon Width
Sub getIconWidth As String
    Return sIconWidth
End Sub
'get Inline Edit
Sub getInlineEdit As Boolean
    Return bInlineEdit
End Sub
'get Multiple Check
Sub getMultipleCheck As Boolean
    Return bMultipleCheck
End Sub
'get Multiple Select
Sub getMultipleSelect As Boolean
    Return bMultipleSelect
End Sub

'set Background Color
Sub setBackgroundColor(s As String)
    sBackgroundColor = s
    CustProps.put("BackgroundColor", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetBackgroundColor(mElement, sBackgroundColor)
End Sub
'set Height
Sub setHeight(s As String)
    sHeight = s
    CustProps.put("Height", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetHeight(mElement, sHeight)
End Sub
'set Rounded
'options: none|rounded|2xl|3xl|full|lg|md|sm|xl|0
Sub setRounded(s As String)
    sRounded = s
    CustProps.put("Rounded", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetRounded(mElement, sRounded)
End Sub
'set Rounded Box
Sub setRoundedBox(b As Boolean)
    bRoundedBox = b
    CustProps.put("RoundedBox", b)
    If mElement = Null Then Return
    UI.SetRoundedBox(mElement, bRoundedBox)
End Sub

'set Shadow
'options: shadow|sm|md|lg|xl|2xl|inner|none
Sub setShadow(s As String)
    sShadow = s
    CustProps.put("Shadow", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetShadow(mElement, sShadow)
End Sub
'set Size
'options: lg|md|none|sm|xl|xs
Sub setSize(s As String)
    sSize = s
    CustProps.put("Size", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetSize(mElement, "size", "menu", sSize)
End Sub
'set Width
Sub setWidth(s As String)
    sWidth = s
    CustProps.put("Width", s)
    If mElement = Null Then Return
    If s <> "" Then UI.SetWidth(mElement, sWidth)
End Sub
'get Background Color
Sub getBackgroundColor As String
    Return sBackgroundColor
End Sub
'get Height
Sub getHeight As String
    Return sHeight
End Sub
'get Rounded
Sub getRounded As String
    Return sRounded
End Sub
'get Rounded Box
Sub getRoundedBox As Boolean
    Return bRoundedBox
End Sub

'get Shadow
Sub getShadow As String
    Return sShadow
End Sub
'get Size
Sub getSize As String
    Return sSize
End Sub
'get Width
Sub getWidth As String
    Return sWidth
End Sub
 
Top