树形结构的checkbox


如上图, 图中元素都为type='checkbox'的input
input1选中, 则下面所有input都要选中, input2选中, 下面的input4input5也都要选中, input2input3都选中, 同样input1也要选中,
这样用js怎么做呢?

阅读 3.4k
2 个回答

在线体验地址

const container = document.createElement("div");
const treeNodeTemplate = (key, label) => `<div id="${key}">
              <label>${label}</label>
              <input type="checkbox" />
              <ul class="children"></ul>
            </div>
            `;
const createTreeNodeFromKeyAndLabel = (key, label) => {
  const tmeplate = treeNodeTemplate(key, label);
  container.innerHTML = tmeplate;

  return container.children[0];
};

const effects = [];
const visited = new Set();
let flushed = false;

class TreeNode {
  elem = null;
  parent = null;
  children = [];
  data = null;
  effects = [];

  constructor(data) {
    this.data = data;
    const uninitialized = this.render(data);
    this.initEvent(uninitialized);
  }

  setCheckboxValue(node, val) {
    const checkbox = this.getCheckboxFromTreeElem(node.elem);
    node.data.checked = val;
    checkbox.checked = val;
  }

  setChildrenValueRecursively(newVal, needTrackSideEffect = false) {
    for (const child of this.children) {
      const elem = child.elem;

      this.setCheckboxValue(child, newVal);
      if (needTrackSideEffect) {
        visited.add(child);
      }
      child.setChildrenValueRecursively(newVal);
    }
  }

  getCheckboxFromTreeElem(elem) {
    return elem.children[1];
  }

  bubbleProperties(newVal) {
    if (!this.parent) return;

    if (newVal) {
      const needUpdate = this.parent.children.every((v) => {
        const checkbox = this.getCheckboxFromTreeElem(v.elem);

        return checkbox.checked;
      });

      if (needUpdate) {
        this.setCheckboxValue(this.parent, newVal);

        this.parent.bubbleProperties(newVal);
      }
    } else {
      this.setCheckboxValue(this.parent, newVal);
      this.parent.bubbleProperties(newVal);
    }
  }

  initEvent(uninitialized) {
    console.assert(!!this.elem, this);

    if (!uninitialized) return;

    const checkbox = this.getCheckboxFromTreeElem(this.elem);
    console.assert(checkbox.tagName === "INPUT", checkbox);

    checkbox.addEventListener("change", (e) => {
      if (e.target.checked) {
        //该节点被切换为true需要将他所有子节点全部置为true
        //并且如果他所在层级所在的节点全部都为true还需要向上冒泡

        //把子节点变为true
        this.setChildrenValueRecursively(true);

        //向上冒泡
        this.bubbleProperties(true);
      } else {
        //把子节点全部变为false
        this.setChildrenValueRecursively(false);

        //向上冒泡
        this.bubbleProperties(false);
      }
      this.data.checked = e.target.checked;
    });
  }

  render(data) {
    let elem = data.__elem;
    const { key, label, children, checked } = data;
    console.assert(!!(key && label), { key, label });
    let uninitialized = elem == null;

    if (checked === true) {
      effects.push(this);
    }

    if (!elem) {
      elem = createTreeNodeFromKeyAndLabel(key, label);
    }

    const childrenContainer = elem.querySelector(".children");

    data.__elem = elem;
    this.elem = elem;

    if (!children) return uninitialized;

    console.assert(Array.isArray(children), children);
    console.assert(!!childrenContainer, childrenContainer);

    for (const child of children) {
      const subTreeNode = new TreeNode(child);
      this.children.push(subTreeNode);
      subTreeNode.parent = this;
      childrenContainer.appendChild(subTreeNode.elem);
    }

    return uninitialized;
  }
}

const treeData = {
  key: "1",
  label: "1",
  children: [
    { key: "2", label: "2", children: [] },
    {
      key: "3",
      label: "3",
      checked: true,
      children: [
        { key: "5", label: "5" },
        { key: "6", label: "6" }
      ]
    },
    { key: "4", label: "4" },
    { key: "7", label: "7", checked: true }
  ]
};

/**
 * 将初始化抽离出来,以防止O(N^2)时间复杂度的初始化更新
 **/
const flushEffects = () => {
  for (let i = 0; i < effects.length; ++i) {
    const effect = effects[i];
    if (visited.has(effect)) continue;
    effect.setCheckboxValue(effect, true);
    visited.add(effect);
    effect.setChildrenValueRecursively(true, true);
  }

  effects.length = 0;
  visited.clear();
};

const renderTree = (data) => {
  const root = new TreeNode(data);
  flushEffects();
  return root;
};

const root = renderTree(treeData);
const app = document.getElementById("app");
app.appendChild(root.elem);
const button = document.getElementById("button");
button.onclick = () => {
  console.log(root.data);
};

思路仅供参考,没有具体测试,先构建一个节点的关系树,当选中一个时,更新自身状态及下级状态,整个状态更新完成后统一渲染上去,整体应该还有优化的空间,节点层级太深也没有考虑

const nodes = {
    el: 'input1',
    checked: false,
    children: [{
        el: 'input2',
        checked: false,
        children: [{
            el: 'input4',
            checked: false
        }, {
            el: 'input5',
            checked: false
        }]
    }, {
        el: 'input3',
        checked: false,
        children: [{
            el: 'input6',
            checked: false
        }]
    }]
};
[...document.querySelector('input[type="checkbox"]')].forEach(checkbox => {
    checkbox.addEventListener('click', function(e) {
        e.preventDefault();
        const id = checkbox.getAttribute('id');
        updateNodesCheckStatus({
            el: id,
            checked: !checkbox.checked
        })
        renderNodes(nodes)
    })
})

function updateNodesCheckStatus(item) {
    if (nodes.el === item.el) {
        nodes.checked = item.checked
        nodes.children.forEach(childNode => {
            childNode.checked = item.checked
            childNode.children.forEach(node => node.checked = item.checked)
        })
        return
    }

    nodes.children.forEach(childNode => {
        if (childNode.el === item.el) {
            // 选中的是一级子节点(input2或input3),则更新自身选中状态和下级选中状态
            childNode.checked === item.checked
            childNode.children.forEach(node => node.checked = item.checked)
        } else {
            const node = childNode.children.find(node => node.el === item.el)
            if (node) {
                // 选中的是二级子节点(input4,input5或input6),更新自身状态和父级状态
                node.checked = item.checked
                childNode.checked = childNode.children.every(node => node.checked)
            }
        }
    })

    // 根据input1的直属input的选中状态来更新input1的状态
    nodes.checked = nodes.children.every(c => c.checked)
}

function renderNodes(nodes) {
    document.querySelector(`#${nodes.el}`).checked = nodes.checked
    nodes.children.forEach(childNode => {
        document.querySelector(`#${childNode.el}`).checked = childNode.checked
        childNode.children.forEach(node => {
            document.querySelector(`#${node.el}`).checked = node.checked
        })
    })
}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题