reliance-react-checkbox-tree
Version:
Fork of checkbox tree in React by Jake Zatecky: https://github.com/jakezatecky/react-checkbox-tree.
242 lines (197 loc) • 7.39 kB
JavaScript
import CheckboxTreeError from './CheckboxTreeError';
import constants from './constants';
const { CheckModel } = constants;
class NodeModel {
constructor(props, nodes = {}) {
this.props = props;
this.flatNodes = nodes;
}
setProps(props) {
this.props = props;
}
clone() {
const clonedNodes = {};
// Re-construct nodes one level deep to avoid shallow copy of mutable characteristics
Object.keys(this.flatNodes).forEach((value) => {
const node = this.flatNodes[value];
clonedNodes[value] = { ...node };
});
return new NodeModel(this.props, clonedNodes);
}
getNode(value) {
return this.flatNodes[value];
}
reset() {
this.flatNodes = {};
}
flattenNodes(nodes, parent = {}, depth = 0) {
if (!Array.isArray(nodes) || nodes.length === 0) {
return;
}
const { disabled, noCascade } = this.props;
// Flatten the `node` property for internal lookups
nodes.forEach((node, index) => {
const isParent = this.nodeHasChildren(node);
// Protect against duplicate node values
if (this.flatNodes[node.value] !== undefined) {
throw new CheckboxTreeError(
`Duplicate value '${node.value}' detected. All node values must be unique.`,
);
}
this.flatNodes[node.value] = {
label: node.label,
value: node.value,
children: node.children,
parent,
isChild: parent.value !== undefined,
isParent,
isLeaf: !isParent,
showCheckbox: node.showCheckbox !== undefined ? node.showCheckbox : true,
disabled: this.getDisabledState(node, parent, disabled, noCascade),
treeDepth: depth,
index,
};
this.flattenNodes(node.children, node, depth + 1);
});
}
nodeHasChildren(node) {
return Array.isArray(node.children);
}
getDisabledState(node, parent, disabledProp, noCascade) {
if (disabledProp) {
return true;
}
if (!noCascade && parent.disabled) {
return true;
}
return Boolean(node.disabled);
}
deserializeLists(lists) {
const listKeys = ['checked', 'expanded'];
// Reset values to false
Object.keys(this.flatNodes).forEach((value) => {
listKeys.forEach((listKey) => {
this.flatNodes[value][listKey] = false;
});
});
// Deserialize values and set their nodes to true
listKeys.forEach((listKey) => {
lists[listKey].forEach((value) => {
if (this.flatNodes[value] !== undefined) {
this.flatNodes[value][listKey] = true;
}
});
});
}
serializeList(key) {
const list = [];
Object.keys(this.flatNodes).forEach((value) => {
if (this.flatNodes[value][key]) {
list.push(value);
}
});
return list;
}
expandAllNodes(expand) {
Object.keys(this.flatNodes).forEach((value) => {
if (this.flatNodes[value].isParent) {
this.flatNodes[value].expanded = expand;
}
});
return this;
}
toggleChecked(node, isChecked, checkModel, noCascade, extraConnections) {
const flatNode = this.flatNodes[node.value];
if (node.disabled) {
return this;
}
// Si ha sido checkear
if (isChecked) {
if (flatNode.isChild && !noCascade) {
this.toggleChecked(
flatNode.parent,
isChecked,
checkModel,
noCascade,
extraConnections,
);
}
this.toggleNode(flatNode.value, 'checked', isChecked);
// Extra connection iteration
const extraConns = extraConnections.filter((conn) => conn.start === flatNode.value);
for (let i = 0, l = extraConns.length; i < l; i += 1) {
const extraConnection = extraConns[i];
this.toggleNode(extraConnection.finish, 'checked', isChecked);
this.toggleChecked(
this.getNode(extraConnection.finish).parent,
isChecked,
checkModel,
noCascade,
extraConnections,
);
}
} else {
if (flatNode.isParent && !noCascade) {
flatNode.children.forEach((child) => {
this.toggleChecked(child, isChecked, checkModel, noCascade, extraConnections);
});
}
this.toggleNode(flatNode.value, 'checked', isChecked);
// Extra connection iteration
const extraConns = extraConnections.filter((conn) => conn.finish === flatNode.value);
for (let i = 0, l = extraConns.length; i < l; i += 1) {
const extraConnection = extraConns[i];
this.toggleNode(extraConnection.start, 'checked', isChecked);
}
}
return this;
}
toggleParentStatus(node, checkModel) {
const flatNode = this.flatNodes[node.value];
if (flatNode.isChild) {
if (checkModel === CheckModel.ALL) {
this.toggleNode(node.value, 'checked', this.isEveryChildChecked(flatNode));
}
this.toggleParentStatus(flatNode.parent, checkModel);
} else {
this.toggleNode(node.value, 'checked', this.isEveryChildChecked(flatNode));
}
}
isEveryChildChecked(node) {
return node.children.every((child) => this.getNode(child.value).checked);
}
toggleNode(nodeValue, key, toggleValue) {
this.flatNodes[nodeValue][key] = toggleValue;
return this;
}
toggleCheckedExclusive(node, isChecked, exceptions) {
const flatNode = this.flatNodes[node.value];
if (node.disabled) {
return this;
}
// Si ha sido checkear
if (isChecked) {
this.clearChecks(node, exceptions);
} else {
this.toggleNode(flatNode.value, 'checked', isChecked);
}
return this;
}
clearChecks(exceptNode, exceptions) {
const key = 'checked';
Object.keys(this.flatNodes).forEach((nodeValue) => {
if (!exceptions.find((exception) => nodeValue.match(exception))) {
this.flatNodes[nodeValue][key] = nodeValue === exceptNode.value;
}
});
return this;
}
clearExclusives(exclusiveNodes) {
const key = 'checked';
Object.values(exclusiveNodes).forEach((node) => {
this.flatNodes[node.value][key] = false;
});
return this;
}
}
export default NodeModel;