#4273 Add support for dataKey on TreeTable (#4936)

pull/4987/head
proxyteng 2023-12-19 14:23:53 +01:00 committed by GitHub
parent f39b351f10
commit 435de17919
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 59 additions and 29 deletions

View File

@ -5,6 +5,12 @@ const TreeTableProps = [
default: 'null', default: 'null',
description: 'An array of treenodes.' description: 'An array of treenodes.'
}, },
{
name: 'dataKey',
type: 'string|function',
default: 'null',
description: 'Name of the field that uniquely identifies the a record in the data.'
},
{ {
name: 'expandedKeys', name: 'expandedKeys',
type: 'array', type: 'array',

View File

@ -10,6 +10,10 @@ export default {
type: null, type: null,
default: null default: null
}, },
dataKey: {
type: [String, Function],
default: 'key'
},
expandedKeys: { expandedKeys: {
type: null, type: null,
default: null default: null

View File

@ -395,6 +395,11 @@ export interface TreeTableProps {
* An array of treenodes. * An array of treenodes.
*/ */
value?: TreeNode[] | undefined; value?: TreeNode[] | undefined;
/**
* Name of the field that uniquely identifies the a record in the data.
* @defaultValue "key"
*/
dataKey?: string | ((item: any) => string) | undefined;
/** /**
* A map of keys to represent the state of the tree expansion state in controlled mode. * A map of keys to represent the state of the tree expansion state in controlled mode.
* @see TreeTableExpandedKeys * @see TreeTableExpandedKeys

View File

@ -83,7 +83,8 @@
<template v-if="!empty"> <template v-if="!empty">
<TTRow <TTRow
v-for="(node, index) of dataToRender" v-for="(node, index) of dataToRender"
:key="node.key" :key="nodeKey(node)"
:dataKey="dataKey"
:columns="columns" :columns="columns"
:node="node" :node="node"
:level="0" :level="0"
@ -252,8 +253,7 @@ export default {
}; };
}, },
onNodeToggle(node) { onNodeToggle(node) {
const key = node.key; const key = this.nodeKey(node);
if (this.d_expandedKeys[key]) { if (this.d_expandedKeys[key]) {
delete this.d_expandedKeys[key]; delete this.d_expandedKeys[key];
this.$emit('node-collapse', node); this.$emit('node-collapse', node);
@ -273,9 +273,13 @@ export default {
this.$emit('update:selectionKeys', _selectionKeys); this.$emit('update:selectionKeys', _selectionKeys);
} }
}, },
nodeKey(node) {
return ObjectUtils.resolveFieldData(node, this.dataKey);
},
handleSelectionWithMetaKey(event) { handleSelectionWithMetaKey(event) {
const originalEvent = event.originalEvent; const originalEvent = event.originalEvent;
const node = event.node; const node = event.node;
const nodeKey = this.nodeKey(node);
const metaKey = originalEvent.metaKey || originalEvent.ctrlKey; const metaKey = originalEvent.metaKey || originalEvent.ctrlKey;
const selected = this.isNodeSelected(node); const selected = this.isNodeSelected(node);
let _selectionKeys; let _selectionKeys;
@ -285,7 +289,7 @@ export default {
_selectionKeys = {}; _selectionKeys = {};
} else { } else {
_selectionKeys = { ...this.selectionKeys }; _selectionKeys = { ...this.selectionKeys };
delete _selectionKeys[node.key]; delete _selectionKeys[nodeKey];
} }
this.$emit('node-unselect', node); this.$emit('node-unselect', node);
@ -296,7 +300,7 @@ export default {
_selectionKeys = !metaKey ? {} : this.selectionKeys ? { ...this.selectionKeys } : {}; _selectionKeys = !metaKey ? {} : this.selectionKeys ? { ...this.selectionKeys } : {};
} }
_selectionKeys[node.key] = true; _selectionKeys[nodeKey] = true;
this.$emit('node-select', node); this.$emit('node-select', node);
} }
@ -304,6 +308,7 @@ export default {
}, },
handleSelectionWithoutMetaKey(event) { handleSelectionWithoutMetaKey(event) {
const node = event.node; const node = event.node;
const nodeKey = this.nodeKey(node);
const selected = this.isNodeSelected(node); const selected = this.isNodeSelected(node);
let _selectionKeys; let _selectionKeys;
@ -313,18 +318,18 @@ export default {
this.$emit('node-unselect', node); this.$emit('node-unselect', node);
} else { } else {
_selectionKeys = {}; _selectionKeys = {};
_selectionKeys[node.key] = true; _selectionKeys[nodeKey] = true;
this.$emit('node-select', node); this.$emit('node-select', node);
} }
} else { } else {
if (selected) { if (selected) {
_selectionKeys = { ...this.selectionKeys }; _selectionKeys = { ...this.selectionKeys };
delete _selectionKeys[node.key]; delete _selectionKeys[nodeKey];
this.$emit('node-unselect', node); this.$emit('node-unselect', node);
} else { } else {
_selectionKeys = this.selectionKeys ? { ...this.selectionKeys } : {}; _selectionKeys = this.selectionKeys ? { ...this.selectionKeys } : {};
_selectionKeys[node.key] = true; _selectionKeys[nodeKey] = true;
this.$emit('node-select', node); this.$emit('node-select', node);
} }
@ -572,7 +577,7 @@ export default {
return matched; return matched;
}, },
isNodeSelected(node) { isNodeSelected(node) {
return this.selectionMode && this.selectionKeys ? this.selectionKeys[node.key] === true : false; return this.selectionMode && this.selectionKeys ? this.selectionKeys[this.nodeKey(node)] === true : false;
}, },
isNodeLeaf(node) { isNodeLeaf(node) {
return node.leaf === false ? false : !(node.children && node.children.length); return node.leaf === false ? false : !(node.children && node.children.length);

View File

@ -41,7 +41,8 @@
<template v-if="expanded && node.children && node.children.length"> <template v-if="expanded && node.children && node.children.length">
<TreeTableRow <TreeTableRow
v-for="childNode of node.children" v-for="childNode of node.children"
:key="childNode.key" :key="nodeKey(childNode)"
:dataKey="dataKey"
:columns="columns" :columns="columns"
:node="childNode" :node="childNode"
:parentNode="node" :parentNode="node"
@ -76,6 +77,10 @@ export default {
type: null, type: null,
default: null default: null
}, },
dataKey: {
type: [String, Function],
default: 'key'
},
parentNode: { parentNode: {
type: null, type: null,
default: null default: null
@ -150,6 +155,9 @@ export default {
onTouchEnd() { onTouchEnd() {
this.nodeTouched = true; this.nodeTouched = true;
}, },
nodeKey(node) {
return ObjectUtils.resolveFieldData(node, this.dataKey);
},
onKeyDown(event, item) { onKeyDown(event, item) {
switch (event.code) { switch (event.code) {
case 'ArrowDown': case 'ArrowDown':
@ -320,8 +328,8 @@ export default {
}); });
}, },
propagateDown(node, check, selectionKeys) { propagateDown(node, check, selectionKeys) {
if (check) selectionKeys[node.key] = { checked: true, partialChecked: false }; if (check) selectionKeys[this.nodeKey(node)] = { checked: true, partialChecked: false };
else delete selectionKeys[node.key]; else delete selectionKeys[this.nodeKey(node)];
if (node.children && node.children.length) { if (node.children && node.children.length) {
for (let child of node.children) { for (let child of node.children) {
@ -336,19 +344,19 @@ export default {
let childPartialSelected = false; let childPartialSelected = false;
for (let child of this.node.children) { for (let child of this.node.children) {
if (_selectionKeys[child.key] && _selectionKeys[child.key].checked) checkedChildCount++; if (_selectionKeys[this.nodeKey(child)] && _selectionKeys[this.nodeKey(child)].checked) checkedChildCount++;
else if (_selectionKeys[child.key] && _selectionKeys[child.key].partialChecked) childPartialSelected = true; else if (_selectionKeys[this.nodeKey(child)] && _selectionKeys[this.nodeKey(child)].partialChecked) childPartialSelected = true;
} }
if (check && checkedChildCount === this.node.children.length) { if (check && checkedChildCount === this.node.children.length) {
_selectionKeys[this.node.key] = { checked: true, partialChecked: false }; _selectionKeys[this.nodeKey(this.node)] = { checked: true, partialChecked: false };
} else { } else {
if (!check) { if (!check) {
delete _selectionKeys[this.node.key]; delete _selectionKeys[this.nodeKey(this.node)];
} }
if (childPartialSelected || (checkedChildCount > 0 && checkedChildCount !== this.node.children.length)) _selectionKeys[this.node.key] = { checked: false, partialChecked: true }; if (childPartialSelected || (checkedChildCount > 0 && checkedChildCount !== this.node.children.length)) _selectionKeys[this.nodeKey(this.node)] = { checked: false, partialChecked: true };
else _selectionKeys[this.node.key] = { checked: false, partialChecked: false }; else _selectionKeys[this.nodeKey(this.node)] = { checked: false, partialChecked: false };
} }
this.$emit('checkbox-change', { this.$emit('checkbox-change', {
@ -364,19 +372,19 @@ export default {
let childPartialSelected = false; let childPartialSelected = false;
for (let child of this.node.children) { for (let child of this.node.children) {
if (_selectionKeys[child.key] && _selectionKeys[child.key].checked) checkedChildCount++; if (_selectionKeys[this.nodeKey(child)] && _selectionKeys[this.nodeKey(child)].checked) checkedChildCount++;
else if (_selectionKeys[child.key] && _selectionKeys[child.key].partialChecked) childPartialSelected = true; else if (_selectionKeys[this.nodeKey(child)] && _selectionKeys[this.nodeKey(child)].partialChecked) childPartialSelected = true;
} }
if (check && checkedChildCount === this.node.children.length) { if (check && checkedChildCount === this.node.children.length) {
_selectionKeys[this.node.key] = { checked: true, partialChecked: false }; _selectionKeys[this.nodeKey(this.node)] = { checked: true, partialChecked: false };
} else { } else {
if (!check) { if (!check) {
delete _selectionKeys[this.node.key]; delete _selectionKeys[this.nodeKey(this.node)];
} }
if (childPartialSelected || (checkedChildCount > 0 && checkedChildCount !== this.node.children.length)) _selectionKeys[this.node.key] = { checked: false, partialChecked: true }; if (childPartialSelected || (checkedChildCount > 0 && checkedChildCount !== this.node.children.length)) _selectionKeys[this.nodeKey(this.node)] = { checked: false, partialChecked: true };
else _selectionKeys[this.node.key] = { checked: false, partialChecked: false }; else _selectionKeys[this.nodeKey(this.node)] = { checked: false, partialChecked: false };
} }
this.$emit('checkbox-change', { this.$emit('checkbox-change', {
@ -402,19 +410,19 @@ export default {
return [this.node.styleClass, this.cx('row')]; return [this.node.styleClass, this.cx('row')];
}, },
expanded() { expanded() {
return this.expandedKeys && this.expandedKeys[this.node.key] === true; return this.expandedKeys && this.expandedKeys[this.nodeKey(this.node)] === true;
}, },
leaf() { leaf() {
return this.node.leaf === false ? false : !(this.node.children && this.node.children.length); return this.node.leaf === false ? false : !(this.node.children && this.node.children.length);
}, },
selected() { selected() {
return this.selectionMode && this.selectionKeys ? this.selectionKeys[this.node.key] === true : false; return this.selectionMode && this.selectionKeys ? this.selectionKeys[this.nodeKey(this.node)] === true : false;
}, },
checked() { checked() {
return this.selectionKeys ? this.selectionKeys[this.node.key] && this.selectionKeys[this.node.key].checked : false; return this.selectionKeys ? this.selectionKeys[this.nodeKey(this.node)] && this.selectionKeys[this.nodeKey(this.node)].checked : false;
}, },
partialChecked() { partialChecked() {
return this.selectionKeys ? this.selectionKeys[this.node.key] && this.selectionKeys[this.node.key].partialChecked : false; return this.selectionKeys ? this.selectionKeys[this.nodeKey(this.node)] && this.selectionKeys[this.nodeKey(this.node)].partialChecked : false;
}, },
getAriaSelected() { getAriaSelected() {
return this.selectionMode === 'single' || this.selectionMode === 'multiple' ? this.selected : null; return this.selectionMode === 'single' || this.selectionMode === 'multiple' ? this.selected : null;

View File

@ -1,7 +1,9 @@
<template> <template>
<DocSectionText v-bind="$attrs"> <DocSectionText v-bind="$attrs">
<p>Selection of multiple nodes via checkboxes is enabled by configuring <i>selectionMode</i> as <i>checkbox</i>.</p> <p>Selection of multiple nodes via checkboxes is enabled by configuring <i>selectionMode</i> as <i>checkbox</i>.</p>
<p>In checkbox selection mode, value binding should be a key-value pair where key is the node key and value is an object that has <i>checked</i> and <i>partialChecked</i> properties to represent the checked state of a node.</p> <p>
In checkbox selection mode, value binding should be a key-value pair where key (or the dataKey) is the node key and value is an object that has <i>checked</i> and <i>partialChecked</i> properties to represent the checked state of a node.
</p>
</DocSectionText> </DocSectionText>
<DocSectionCode :code="introCode" hideToggleCode importCode hideCodeSandbox hideStackBlitz /> <DocSectionCode :code="introCode" hideToggleCode importCode hideCodeSandbox hideStackBlitz />
<div class="card"> <div class="card">