570 lines
15 KiB
JavaScript
570 lines
15 KiB
JavaScript
import { getPropsData, getAllProps, getKey, getAttrs, getSlotOptions, filterEmpty, getSlots } from '../../_util/props-util'
|
|
import { cloneVNodes, cloneElement } from '../../_util/vnode'
|
|
export function toTitle (title) {
|
|
if (typeof title === 'string') {
|
|
return title
|
|
}
|
|
return null
|
|
}
|
|
|
|
export function getValuePropValue (child) {
|
|
const props = getAllProps(child)
|
|
if ('value' in props) {
|
|
return props.value
|
|
}
|
|
if (getKey(child) !== undefined) {
|
|
return getKey(child)
|
|
}
|
|
throw new Error(`no key or value for ${child}`)
|
|
}
|
|
|
|
export function getPropValue (child, prop) {
|
|
if (prop === 'value') {
|
|
return getValuePropValue(child)
|
|
}
|
|
const slots = getSlots(child)
|
|
if (prop === 'children') {
|
|
const newChild = child.$slots ? cloneVNodes(child.$slots.default, true) : cloneVNodes(child.componentOptions.children, true)
|
|
if (newChild.length === 1 && !newChild[0].tag) {
|
|
return newChild[0].text
|
|
}
|
|
return newChild
|
|
}
|
|
if (slots[prop]) {
|
|
return cloneVNodes(slots[prop], true)
|
|
}
|
|
const data = getPropsData(child)
|
|
if (prop in data) {
|
|
return data[prop]
|
|
} else {
|
|
return getAttrs(child)[prop]
|
|
}
|
|
}
|
|
|
|
export function isMultiple (props) {
|
|
return !!(props.multiple || props.treeCheckable)
|
|
}
|
|
|
|
export function toArray (value) {
|
|
let ret = value
|
|
if (value === undefined) {
|
|
ret = []
|
|
} else if (!Array.isArray(value)) {
|
|
ret = [value]
|
|
}
|
|
return ret
|
|
}
|
|
|
|
export function preventDefaultEvent (e) {
|
|
e.preventDefault()
|
|
}
|
|
|
|
export const UNSELECTABLE_STYLE = {
|
|
userSelect: 'none',
|
|
WebkitUserSelect: 'none',
|
|
}
|
|
|
|
export const UNSELECTABLE_ATTRIBUTE = {
|
|
unselectable: 'unselectable',
|
|
}
|
|
|
|
export function labelCompatible (prop) {
|
|
let newProp = prop
|
|
if (newProp === 'label') {
|
|
newProp = 'title'
|
|
}
|
|
return newProp
|
|
}
|
|
|
|
export function isInclude (smallArray, bigArray) {
|
|
// attention: [0,0,1] [0,0,10]
|
|
return smallArray.every((ii, i) => {
|
|
return ii === bigArray[i]
|
|
})
|
|
}
|
|
|
|
export function isPositionPrefix (smallPos, bigPos) {
|
|
if (!bigPos || !smallPos) {
|
|
// console.log(smallPos, bigPos);
|
|
return false
|
|
}
|
|
if (bigPos.length < smallPos.length) {
|
|
return false
|
|
}
|
|
// attention: "0-0-1" "0-0-10"
|
|
if ((bigPos.length > smallPos.length) && (bigPos.charAt(smallPos.length) !== '-')) {
|
|
return false
|
|
}
|
|
return bigPos.substr(0, smallPos.length) === smallPos
|
|
}
|
|
|
|
/*
|
|
export function getCheckedKeys(node, checkedKeys, allCheckedNodesKeys) {
|
|
const nodeKey = node.props.eventKey;
|
|
let newCks = [...checkedKeys];
|
|
let nodePos;
|
|
const unCheck = allCheckedNodesKeys.some(item => {
|
|
if (item.key === nodeKey) {
|
|
nodePos = item.pos;
|
|
return true;
|
|
}
|
|
});
|
|
if (unCheck) {
|
|
newCks = [];
|
|
allCheckedNodesKeys.forEach(item => {
|
|
if (isPositionPrefix(item.pos, nodePos) || isPositionPrefix(nodePos, item.pos)) {
|
|
return;
|
|
}
|
|
newCks.push(item.key);
|
|
});
|
|
} else {
|
|
newCks.push(nodeKey);
|
|
}
|
|
return newCks;
|
|
}
|
|
*/
|
|
|
|
function getChildrenlength (children) {
|
|
let len = 1
|
|
if (Array.isArray(children)) {
|
|
len = children.length
|
|
}
|
|
return len
|
|
}
|
|
|
|
function getSiblingPosition (index, len, siblingPosition) {
|
|
if (len === 1) {
|
|
siblingPosition.first = true
|
|
siblingPosition.last = true
|
|
} else {
|
|
siblingPosition.first = index === 0
|
|
siblingPosition.last = index === len - 1
|
|
}
|
|
return siblingPosition
|
|
}
|
|
|
|
function filterChild (childs) {
|
|
const newChilds = []
|
|
childs.forEach(child => {
|
|
const options = getSlotOptions(child)
|
|
if (options.__ANT_TREE_NODE || options.__ANT_TREE_SELECT_NODE) {
|
|
newChilds.push(child)
|
|
}
|
|
})
|
|
return newChilds
|
|
}
|
|
|
|
export function loopAllChildren (childs, callback, parent) {
|
|
const loop = (children, level, _parent) => {
|
|
const len = getChildrenlength(children)
|
|
children.forEach(function handler(item, index) { // eslint-disable-line
|
|
const pos = `${level}-${index}`
|
|
if (item && item.componentOptions && item.componentOptions.children) {
|
|
loop(filterChild(item.componentOptions.children), pos, { node: item, pos })
|
|
}
|
|
if (item) {
|
|
callback(item, index, pos, item.key || pos, getSiblingPosition(index, len, {}), _parent)
|
|
}
|
|
})
|
|
}
|
|
loop(filterChild(childs), 0, parent)
|
|
}
|
|
|
|
// export function loopAllChildren(childs, callback) {
|
|
// const loop = (children, level) => {
|
|
// React.Children.forEach(children, (item, index) => {
|
|
// const pos = `${level}-${index}`;
|
|
// if (item && item.props.children) {
|
|
// loop(item.props.children, pos);
|
|
// }
|
|
// if (item) {
|
|
// callback(item, index, pos, getValuePropValue(item));
|
|
// }
|
|
// });
|
|
// };
|
|
// loop(childs, 0);
|
|
// }
|
|
|
|
// TODO: Here has the side effect. Update node children data affect.
|
|
export function flatToHierarchy (arr) {
|
|
if (!arr.length) {
|
|
return arr
|
|
}
|
|
const hierarchyNodes = []
|
|
const levelObj = {}
|
|
arr.forEach((item) => {
|
|
if (!item.pos) {
|
|
return
|
|
}
|
|
const posLen = item.pos.split('-').length
|
|
if (!levelObj[posLen]) {
|
|
levelObj[posLen] = []
|
|
}
|
|
levelObj[posLen].push(item)
|
|
})
|
|
const levelArr = Object.keys(levelObj).sort((a, b) => b - a)
|
|
// const s = Date.now();
|
|
// todo: there are performance issues!
|
|
levelArr.reduce((pre, cur) => {
|
|
if (cur && cur !== pre) {
|
|
levelObj[pre].forEach((item) => {
|
|
let haveParent = false
|
|
levelObj[cur].forEach((ii) => {
|
|
if (isPositionPrefix(ii.pos, item.pos)) {
|
|
haveParent = true
|
|
if (!ii.children) {
|
|
ii.children = []
|
|
}
|
|
ii.children.push(item)
|
|
}
|
|
})
|
|
if (!haveParent) {
|
|
hierarchyNodes.push(item)
|
|
}
|
|
})
|
|
}
|
|
return cur
|
|
})
|
|
// console.log(Date.now() - s);
|
|
return levelObj[levelArr[levelArr.length - 1]].concat(hierarchyNodes)
|
|
}
|
|
|
|
// arr.length === 628, use time: ~20ms
|
|
export function filterParentPosition (arr) {
|
|
const levelObj = {}
|
|
arr.forEach((item) => {
|
|
const posLen = item.split('-').length
|
|
if (!levelObj[posLen]) {
|
|
levelObj[posLen] = []
|
|
}
|
|
levelObj[posLen].push(item)
|
|
})
|
|
const levelArr = Object.keys(levelObj).sort()
|
|
for (let i = 0; i < levelArr.length; i++) {
|
|
if (levelArr[i + 1]) {
|
|
levelObj[levelArr[i]].forEach(ii => {
|
|
for (let j = i + 1; j < levelArr.length; j++) {
|
|
levelObj[levelArr[j]].forEach((_i, index) => {
|
|
if (isPositionPrefix(ii, _i)) {
|
|
levelObj[levelArr[j]][index] = null
|
|
}
|
|
})
|
|
levelObj[levelArr[j]] = levelObj[levelArr[j]].filter(p => p)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
let nArr = []
|
|
levelArr.forEach(i => {
|
|
nArr = nArr.concat(levelObj[i])
|
|
})
|
|
return nArr
|
|
}
|
|
// console.log(filterParentPosition(
|
|
// ['0-2', '0-3-3', '0-10', '0-10-0', '0-0-1', '0-0', '0-1-1', '0-1']
|
|
// ));
|
|
|
|
function stripTail (str) {
|
|
const arr = str.match(/(.+)(-[^-]+)$/)
|
|
let st = ''
|
|
if (arr && arr.length === 3) {
|
|
st = arr[1]
|
|
}
|
|
return st
|
|
}
|
|
function splitPosition (pos) {
|
|
return pos.split('-')
|
|
}
|
|
|
|
// todo: do optimization.
|
|
export function handleCheckState (obj, checkedPositionArr, checkIt) {
|
|
// console.log(stripTail('0-101-000'));
|
|
// let s = Date.now();
|
|
let objKeys = Object.keys(obj)
|
|
|
|
objKeys.forEach((i, index) => {
|
|
const iArr = splitPosition(i)
|
|
let saved = false
|
|
checkedPositionArr.forEach((_pos) => {
|
|
const _posArr = splitPosition(_pos)
|
|
if (iArr.length > _posArr.length && isInclude(_posArr, iArr)) {
|
|
obj[i].halfChecked = false
|
|
obj[i].checked = checkIt
|
|
objKeys[index] = null
|
|
}
|
|
if (iArr[0] === _posArr[0] && iArr[1] === _posArr[1]) {
|
|
saved = true
|
|
}
|
|
})
|
|
if (!saved) {
|
|
objKeys[index] = null
|
|
}
|
|
})
|
|
objKeys = objKeys.filter(i => i) // filter non null;
|
|
|
|
for (let pIndex = 0; pIndex < checkedPositionArr.length; pIndex++) {
|
|
// loop to set ancestral nodes's `checked` or `halfChecked`
|
|
const loop = (__pos) => {
|
|
const _posLen = splitPosition(__pos).length
|
|
if (_posLen <= 2) { // e.g. '0-0', '0-1'
|
|
return
|
|
}
|
|
let sibling = 0
|
|
let siblingChecked = 0
|
|
const parentPosition = stripTail(__pos)
|
|
objKeys.forEach((i /* , index*/) => {
|
|
const iArr = splitPosition(i)
|
|
if (iArr.length === _posLen && isInclude(splitPosition(parentPosition), iArr)) {
|
|
sibling++
|
|
if (obj[i].checked) {
|
|
siblingChecked++
|
|
const _i = checkedPositionArr.indexOf(i)
|
|
if (_i > -1) {
|
|
checkedPositionArr.splice(_i, 1)
|
|
if (_i <= pIndex) {
|
|
pIndex--
|
|
}
|
|
}
|
|
} else if (obj[i].halfChecked) {
|
|
siblingChecked += 0.5
|
|
}
|
|
// objKeys[index] = null;
|
|
}
|
|
})
|
|
// objKeys = objKeys.filter(i => i); // filter non null;
|
|
const parent = obj[parentPosition]
|
|
// not check, checked, halfChecked
|
|
if (siblingChecked === 0) {
|
|
parent.checked = false
|
|
parent.halfChecked = false
|
|
} else if (siblingChecked === sibling) {
|
|
parent.checked = true
|
|
parent.halfChecked = false
|
|
} else {
|
|
parent.halfChecked = true
|
|
parent.checked = false
|
|
}
|
|
loop(parentPosition)
|
|
}
|
|
loop(checkedPositionArr[pIndex], pIndex)
|
|
}
|
|
// console.log(Date.now()-s, objKeys.length, checkIt);
|
|
}
|
|
|
|
function getCheck (treeNodesStates, checkedPositions) {
|
|
const halfCheckedKeys = []
|
|
const checkedKeys = []
|
|
const checkedNodes = []
|
|
Object.keys(treeNodesStates).forEach((item) => {
|
|
const itemObj = treeNodesStates[item]
|
|
if (itemObj.checked) {
|
|
checkedKeys.push(itemObj.key)
|
|
// checkedNodes.push(getValuePropValue(itemObj.node));
|
|
checkedNodes.push({ ...itemObj, pos: item })
|
|
} else if (itemObj.halfChecked) {
|
|
halfCheckedKeys.push(itemObj.key)
|
|
}
|
|
})
|
|
return {
|
|
halfCheckedKeys, checkedKeys, checkedNodes, treeNodesStates, checkedPositions,
|
|
}
|
|
}
|
|
|
|
export function getTreeNodesStates (children, values) {
|
|
const checkedPositions = []
|
|
const treeNodesStates = {}
|
|
loopAllChildren(children, (item, index, pos, keyOrPos, siblingPosition) => {
|
|
treeNodesStates[pos] = {
|
|
node: item,
|
|
key: keyOrPos,
|
|
checked: false,
|
|
halfChecked: false,
|
|
siblingPosition,
|
|
}
|
|
if (values.indexOf(getValuePropValue(item)) !== -1) {
|
|
treeNodesStates[pos].checked = true
|
|
checkedPositions.push(pos)
|
|
}
|
|
})
|
|
|
|
handleCheckState(treeNodesStates, filterParentPosition(checkedPositions.sort()), true)
|
|
|
|
return getCheck(treeNodesStates, checkedPositions)
|
|
}
|
|
|
|
// can add extra prop to every node.
|
|
export function recursiveCloneChildren (children, cb = ch => ch) {
|
|
// return React.Children.map(children, child => {
|
|
return Array.from(children).map(child => {
|
|
const newChild = cb(child)
|
|
if (newChild && newChild.props && newChild.props.children) {
|
|
return cloneElement(newChild, {
|
|
children: recursiveCloneChildren(newChild.props.children, cb),
|
|
})
|
|
}
|
|
return newChild
|
|
})
|
|
}
|
|
// const newChildren = recursiveCloneChildren(children, child => {
|
|
// const extraProps = {};
|
|
// if (child && child.type && child.type.xxx) {
|
|
// extraProps._prop = true;
|
|
// return React.cloneElement(child, extraProps);
|
|
// }
|
|
// return child;
|
|
// });
|
|
|
|
function recursiveGen (children, level = 0) {
|
|
return children.map((child, index) => {
|
|
const pos = `${level}-${index}`
|
|
const props = getAllProps(child)
|
|
const { title, label, value, ...rest } = props
|
|
const { children: subChildren } = child.componentOptions
|
|
const o = {
|
|
...rest,
|
|
title,
|
|
label: label || title,
|
|
value,
|
|
key: child.key,
|
|
_pos: pos,
|
|
}
|
|
if (subChildren) {
|
|
o.children = recursiveGen(subChildren, pos)
|
|
}
|
|
return o
|
|
})
|
|
}
|
|
|
|
function recursive (children, cb) {
|
|
children.forEach(item => {
|
|
cb(item)
|
|
if (item.children) {
|
|
recursive(item.children, cb)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Get the tree's checkedNodes (todo: can merge to the `handleCheckState` function)
|
|
// If one node checked, it's all children nodes checked.
|
|
// If sibling nodes all checked, the parent checked.
|
|
export function filterAllCheckedData (vs, treeNodes) {
|
|
const vals = [...vs]
|
|
if (!vals.length) {
|
|
return vals
|
|
}
|
|
|
|
const data = recursiveGen(treeNodes)
|
|
const checkedNodesPositions = []
|
|
|
|
function checkChildren (children) {
|
|
children.forEach(item => {
|
|
if (item.__checked) {
|
|
return
|
|
}
|
|
const ci = vals.indexOf(item.value)
|
|
const childs = item.children
|
|
if (ci > -1) {
|
|
item.__checked = true
|
|
checkedNodesPositions.push({ node: item, pos: item._pos })
|
|
vals.splice(ci, 1)
|
|
if (childs) {
|
|
recursive(childs, child => {
|
|
child.__checked = true
|
|
checkedNodesPositions.push({ node: child, pos: child._pos })
|
|
})
|
|
}
|
|
} else {
|
|
if (childs) {
|
|
checkChildren(childs)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
function checkParent (children, parent = { root: true }) {
|
|
let siblingChecked = 0
|
|
children.forEach(item => {
|
|
const childs = item.children
|
|
if (childs && !item.__checked && !item.__halfChecked) {
|
|
const p = checkParent(childs, item)
|
|
if (p.__checked) {
|
|
siblingChecked++
|
|
} else if (p.__halfChecked) {
|
|
siblingChecked += 0.5
|
|
}
|
|
} else if (item.__checked) {
|
|
siblingChecked++
|
|
} else if (item.__halfChecked) {
|
|
siblingChecked += 0.5
|
|
}
|
|
})
|
|
const len = children.length
|
|
if (siblingChecked === len) {
|
|
parent.__checked = true
|
|
checkedNodesPositions.push({ node: parent, pos: parent._pos })
|
|
} else if (siblingChecked < len && siblingChecked > 0) {
|
|
parent.__halfChecked = true
|
|
}
|
|
if (parent.root) {
|
|
return children
|
|
}
|
|
return parent
|
|
}
|
|
checkChildren(data)
|
|
checkParent(data)
|
|
|
|
checkedNodesPositions.forEach((i, index) => {
|
|
// clear private metadata
|
|
delete checkedNodesPositions[index].node.__checked
|
|
delete checkedNodesPositions[index].node._pos
|
|
// create the same structure of `onCheck`'s return.
|
|
checkedNodesPositions[index].node.props = {
|
|
title: checkedNodesPositions[index].node.title,
|
|
label: checkedNodesPositions[index].node.label || checkedNodesPositions[index].node.title,
|
|
value: checkedNodesPositions[index].node.value,
|
|
}
|
|
if (checkedNodesPositions[index].node.children) {
|
|
checkedNodesPositions[index].node.props.children = checkedNodesPositions[index].node.children
|
|
}
|
|
delete checkedNodesPositions[index].node.title
|
|
delete checkedNodesPositions[index].node.label
|
|
delete checkedNodesPositions[index].node.value
|
|
delete checkedNodesPositions[index].node.children
|
|
})
|
|
return checkedNodesPositions
|
|
}
|
|
|
|
export function processSimpleTreeData (treeData, format) {
|
|
function unflatten2 (array, parent = { [format.id]: format.rootPId }) {
|
|
const children = []
|
|
for (let i = 0; i < array.length; i++) {
|
|
array[i] = { ...array[i] } // copy, can not corrupts original data
|
|
if (array[i][format.pId] === parent[format.id]) {
|
|
array[i].key = array[i][format.id]
|
|
children.push(array[i])
|
|
array.splice(i--, 1)
|
|
}
|
|
}
|
|
if (children.length) {
|
|
parent.children = children
|
|
children.forEach(child => unflatten2(array, child))
|
|
}
|
|
if (parent[format.id] === format.rootPId) {
|
|
return children
|
|
}
|
|
}
|
|
return unflatten2(treeData)
|
|
}
|
|
|
|
export function saveRef (instance, name) {
|
|
if (!instance.saveRefs) {
|
|
instance.saveRefs = {}
|
|
}
|
|
if (!instance.saveRefs[name]) {
|
|
instance.saveRefs[name] = (node) => {
|
|
instance[name] = node
|
|
}
|
|
}
|
|
return instance.saveRefs[name]
|
|
}
|