qlik-oss/sn-org-chart

View on GitHub
src/tree/box.js

Summary

Maintainability
A
2 hrs
Test Coverage
F
43%
/* eslint-disable no-undef */
import card from "../card/card";
import selections from "../utils/selections";
import { haveNoChildren } from "../utils/tree-utils";
import constants from "./size-constants";
import { closeTooltip, openTooltip } from "./tooltip";

export const getSign = (d, { topId, isExpanded, expandedChildren }, ancestorIds) => {
  if (
    (d.data.id === topId && isExpanded) ||
    (d.parent && d.parent.data.id === topId && expandedChildren.includes(d.data.id)) ||
    ancestorIds.includes(d.data.id)
  ) {
    return "-";
  }

  return "+";
};

export const getNewState = (d, { topId, isExpanded, expandedChildren }, ancestorIds) => {
  if (d.data.id === topId) {
    // top
    isExpanded = !isExpanded;
    expandedChildren = [];
  } else if (ancestorIds.includes(d.data.id)) {
    // ancestors
    topId = d.parent ? d.parent.data.id : d.data.id;
    isExpanded = !!d.parent;
    expandedChildren = [];
  } else if (d.parent.data.id === topId) {
    // children
    const expandedHaveNoChildren = d.parent.children
      .filter((sibling) => expandedChildren.includes(sibling.data.id))
      .every((n) => haveNoChildren(n.children));
    if (expandedChildren.includes(d.data.id)) {
      // Collapse if already exists in expandedChildren
      expandedChildren.splice(expandedChildren.indexOf(d.data.id), 1);
    } else if (haveNoChildren(d.children) && expandedHaveNoChildren) {
      // Add this node as expanded if possible
      expandedChildren.push(d.data.id);
    } else {
      // Replace expanded with this node
      expandedChildren = [d.data.id];
    }
  } else {
    // grand children
    topId = d.parent.data.id;
    isExpanded = true;
    expandedChildren = [d.data.id];
  }

  return { topId, isExpanded, expandedChildren };
};

export const getNewUpState = (d, isExpanded) => ({
  topId: d.parent.data.id,
  expandedChildren: isExpanded ? [d.data.id] : [],
  isExpanded: true,
});

export default function box({
  positioning,
  divBox,
  nodes,
  styling,
  setExpandedCallback,
  wrapperState,
  selectionObj,
  navigationMode,
  element,
  tooltip,
}) {
  const { x, y } = positioning;
  const { cardWidth, cardHeight, buttonWidth, buttonHeight, cardPadding, rootDiameter } = constants;
  const { topId } = wrapperState.expandedState;
  const topNode = nodes.find((node) => node.data.id === topId);
  const ancestorIds = topNode && topNode.parent ? topNode.parent.ancestors().map((anc) => anc.data.id) : [];
  const touchmode = document.getElementsByTagName("html")[0].classList.contains("touch-on");
  // dummy root
  divBox
    .selectAll(".sn-org-nodes")
    .data(nodes.filter((node) => node.parent && node.parent.data.id === "Root"))
    .enter()
    .append("div")
    .attr("class", "sn-org-root")
    .attr("style", (d) => `top:${y(d) - rootDiameter - cardPadding}px;left:${x(d) + (cardWidth - rootDiameter) / 2}px`)
    .attr("id", (d) => d.data.id);

  // cards
  divBox
    .selectAll(".sn-org-nodes")
    .data(nodes.filter((node) => node.data.id !== "Root"))
    .enter()
    .append("div")
    .attr("class", "sn-org-card")
    .attr("style", (d) => `width:${cardWidth}px;height:${cardHeight}px; top:${y(d)}px;left:${x(d)}px;`)
    .attr("id", (d) => d.data.id)
    .on("click", (event, node) => {
      if (!wrapperState.constraints.active && node.data.id !== "Root") {
        touchmode && openTooltip(tooltip, node, element.clientHeight, styling, x, y, wrapperState.transform, 0);
        selections.select(node, selectionObj);
      }
    })
    .html((d) => card(d.data, styling, selectionObj))
    .on("mouseenter", (event, d) => {
      if (!touchmode && !wrapperState.constraints.active && event.buttons === 0) {
        openTooltip(tooltip, d, element.clientHeight, styling, x, y, wrapperState.transform);
      }
    })
    .on("mouseleave", () => {
      !touchmode && closeTooltip(tooltip);
    })
    .on("mousedown", () => {
      closeTooltip(tooltip);
    })
    .on("touchmove", () => {
      closeTooltip(tooltip);
    })
    .on("wheel", () => {
      closeTooltip(tooltip);
    });

  // expand/collapse
  if (navigationMode !== "expandAll") {
    divBox
      .selectAll(".sn-org-nodes")
      .data(nodes.filter((node) => !!node.children && node.data.id !== "Root"))
      .enter()
      .append("div")
      .attr("class", "sn-org-traverse")
      .attr(
        "style",
        (d) =>
          `width:${buttonWidth}px;height:${buttonHeight}px;top:${y(d) + cardHeight + cardPadding}px;left:${
            x(d) + (cardWidth - buttonWidth) / 2
          }px;`,
      )
      .attr("id", (d) => `${d.data.id}-expand`)
      .on("mouseenter", (event) => {
        if (!wrapperState.constraints.active) event.target.style.cursor = "pointer";
      })
      .on("click", (event, d) => {
        if (!wrapperState.constraints.active) {
          setExpandedCallback(getNewState(d, wrapperState.expandedState, ancestorIds));
          event.stopPropagation();
        }
      })
      .html((d) => `${getSign(d, wrapperState.expandedState, ancestorIds)} ${d.data.children.length}`);
  }
  // go up only necessary in page navigation mode
  // if (navigationMode !== 'free') {
  //   divBox
  //     .selectAll('.sn-org-nodes')
  //     .data(nodes.filter(node => node.data.id === topId && node.parent))
  //     .enter()
  //     .append('div')
  //     .attr('class', 'sn-org-traverse')
  //     .attr(
  //       'style',
  //       d =>
  //         `width:${buttonWidth}px;height:${buttonHeight}px;top:${y(d) - buttonHeight - cardPadding}px;left:${x(d) +
  //           (cardWidth - buttonWidth) / 2}px;`
  //     )
  //     .attr('id', d => `${d.data.id}-up`)
  //     .on('click', d => {
  //       if (!wrapperState.constraints.active) {
  //         setExpandedCallback(getNewUpState(d, isExpanded));
  //       }
  //     })
  //     .html('↑');
  // }
}