Initiated treeTable

pull/41/head
cagataycivici 2019-08-05 21:12:29 +03:00
parent 5c045d2fc4
commit fb366ea68a
11 changed files with 1469 additions and 1 deletions

View File

@ -63,6 +63,7 @@
<router-link to="/paginator">&#9679; Paginator</router-link> <router-link to="/paginator">&#9679; Paginator</router-link>
<router-link to="/picklist">&#9679; PickList</router-link> <router-link to="/picklist">&#9679; PickList</router-link>
<router-link to="/tree">&#9679; Tree</router-link> <router-link to="/tree">&#9679; Tree</router-link>
<router-link to="/treetable">&#9679; TreeTable</router-link>
</div> </div>
</div> </div>
</transition> </transition>

View File

@ -61,6 +61,10 @@ export default {
selectionMode: { selectionMode: {
type: String, type: String,
default: null default: null
},
expander: {
type: Boolean,
default: false
} }
}, },
render() { render() {

View File

View File

@ -0,0 +1,469 @@
<template>
<div :class="containerClass">
<slot></slot>
<div class="p-treetable-header" v-if="$scopedSlots.header">
<slot name="header"></slot>
</div>
<div class="p-treetable-wrapper">
<table>
<thead class="p-treetable-thead">
<tr>
<th v-for="(col,i) of columns" :key="col.columnKey||col.field||i" :style="col.headerStyle" :class="getColumnHeaderClass(col)" @click="onColumnHeaderClick($event, col)">
<TTColumnSlot :column="col" type="header" v-if="col.$scopedSlots.header" />
<span class="p-column-title" v-if="col.header">{{col.header}}</span>
<span v-if="col.sortable" :class="getSortableColumnIcon(col)"></span>
<TTColumnSlot :column="col" type="filter" v-if="col.$scopedSlots.filter" />
</th>
</tr>
</thead>
<tfoot class="p-treetable-tfoot" v-if="hasFooter">
<tr>
<td v-for="(col,i) of columns" :key="col.columnKey||col.field||i" :style="col.footerStyle" :class="col.footerClass">
<TTColumnSlot :column="col" type="footer" v-if="col.$scopedSlots.footer" />
{{col.footer}}
</td>
</tr>
</tfoot>
<tbody class="p-treetable-tbody">
<template v-if="!empty">
<TTRow v-for="node of value" :key="node.key" :columns="columns" :node="node" :level="0"
:expandedKeys="d_expandedKeys" @node-toggle="onNodeToggle"
:selectionMode="selectionMode" :selectionKeys="selectionKeys"></TTRow>
</template>
<tr v-else class="p-treetable-emptymessage">
<td :colspan="columns.length">
<slot name="empty"></slot>
</td>
</tr>
</tbody>
</table>
</div>
<div class="p-treetable-footer" v-if="$scopedSlots.header">
<slot name="footer"></slot>
</div>
</div>
</template>
<script>
import TreeTableColumnSlot from './TreeTableColumnSlot';
import TreeTableRow from './TreeTableRow';
export default {
props: {
value: {
type: null,
default: null
},
expandedKeys: {
type: null,
default: null
},
selectionKeys: {
type: null,
default: null
},
selectionMode: {
type: String,
default: null
},
metaKeySelection: {
type: Boolean,
default: true
},
loading: {
type: Boolean,
default: false
},
loadingIcon: {
type: String,
default: 'pi pi-spinner'
},
rowHover: {
type: Boolean,
default: false
},
autoLayout: {
type: Boolean,
default: false
},
sortField: {
type: String,
default: null
},
sortOrder: {
type: Number,
default: null
},
defaultSortOrder: {
type: Number,
default: 1
},
multiSortMeta: {
type: Array,
default: null
},
sortMode: {
type: String,
default: 'single'
}
},
data() {
return {
allChildren: null,
d_expandedKeys: this.expandedKeys || {},
d_first: this.first,
d_rows: this.rows,
d_sortField: this.sortField,
d_sortOrder: this.sortOrder,
d_multiSortMeta: this.multiSortMeta ? [...this.multiSortMeta] : [],
}
},
watch: {
expandedKeys(newValue) {
this.d_expandedKeys = newValue;
},
first(newValue) {
this.d_first = newValue;
},
rows(newValue) {
this.d_rows = newValue;
},
sortField(newValue) {
this.d_sortField = newValue;
},
sortOrder(newValue) {
this.d_sortOrder = newValue;
},
multiSortMeta(newValue) {
this.d_multiSortMeta = newValue;
}
},
mounted() {
this.allChildren = this.$children;
},
methods: {
onNodeToggle(node) {
const key = node.key;
if (this.d_expandedKeys[key]) {
delete this.d_expandedKeys[key];
this.$emit('node-collapse', node);
}
else {
this.d_expandedKeys[key] = true;
this.$emit('node-expand', node);
}
this.d_expandedKeys = {...this.d_expandedKeys};
this.$emit('update:expandedKeys', this.d_expandedKeys);
},
getColumnHeaderClass(column) {
const sorted = this.sortMode === 'single' ? (this.d_sortField === (column.field || column.sortField)) : this.getMultiSortMetaIndex(column) > -1;
return [column.headerClass,
{'p-sortable-column': column.sortable},
{'p-highlight': sorted}
];
},
getSortableColumnIcon(column) {
let sorted = false;
let sortOrder = null;
if (this.sortMode === 'single') {
sorted = this.d_sortField === (column.field || column.sortField);
sortOrder = sorted ? this.d_sortOrder: 0;
}
else if (this.sortMode === 'multiple') {
let metaIndex = this.getMultiSortMetaIndex(column);
if (metaIndex > -1) {
sorted = true;
sortOrder = this.d_multiSortMeta[metaIndex].order;
}
}
return [
'p-sortable-column-icon pi pi-fw',
{'pi-sort': !sorted},
{'pi-sort-up': sorted && sortOrder > 0},
{'pi-sort-down': sorted && sortOrder < 0},
];
},
getMultiSortMetaIndex(column) {
let index = -1;
for (let i = 0; i < this.d_multiSortMeta.length; i++) {
let meta = this.d_multiSortMeta[i];
if (meta.field === (column.field || column.sortField)) {
index = i;
break;
}
}
return index;
},
onColumnHeaderClick() {
},
isNodeSelected() {
return false;
}
},
computed: {
containerClass() {
return ['p-treetable p-component', {
'p-treetable-hoverable-rows': (this.rowHover || this.selectionMode),
'p-treetable-auto-layout': this.autoLayout
}];
},
columns() {
if (this.allChildren) {
return this.allChildren.filter(child => child.$options._propKeys.indexOf('columnKey') !== -1);
}
return [];
},
processedData() {
return this.value;
},
empty() {
const data = this.processedData;
return (!data || data.length === 0);
},
hasFooter() {
let hasFooter = false;
for (let col of this.columns) {
if (col.footer || col.$scopedSlots.footer) {
hasFooter = true;
break;
}
}
return hasFooter;
}
},
components: {
'TTColumnSlot': TreeTableColumnSlot,
'TTRow': TreeTableRow
}
}
</script>
<style>
.p-treetable {
position: relative;
}
.p-treetable table {
border-collapse: collapse;
width: 100%;
table-layout: fixed;
}
.p-treetable-toggler {
cursor: pointer;
display: inline-block;
}
.p-treetable .p-treetable-thead > tr > th,
.p-treetable .p-treetable-tbody > tr > td,
.p-treetable .p-treetable-tfoot > tr > td {
padding: .25em .5em;
}
.p-treetable .p-treetable-thead > tr > th .p-column-title {
vertical-align: middle;
}
.p-treetable .p-sortable-column {
cursor: pointer;
}
.p-treetable .p-sortable-column-icon {
vertical-align: middle;
}
.p-treetable-auto-layout > .p-treetable-wrapper {
overflow-x: auto;
}
.p-treetable-auto-layout > .p-treetable-wrapper > table {
table-layout: auto;
}
/* Sections */
.p-treetable-header,
.p-treetable-footer {
padding: .25em .5em;
text-align: center;
font-weight: bold;
}
.p-treetable-header {
border-bottom: 0 none;
}
.p-treetable-footer {
border-top: 0 none;
}
/* Paginator */
.p-treetable .p-paginator-top {
border-bottom: 0 none;
}
.p-treetable .p-paginator-bottom {
border-top: 0 none;
}
/* Scrollable */
.p-treetable-scrollable-wrapper {
position: relative;
}
.p-treetable-scrollable-header,
.p-treetable-scrollable-footer {
overflow: hidden;
border: 0 none;
}
.p-treetable-scrollable-body {
overflow: auto;
position: relative;
}
.p-treetable-scrollable-body > table > .p-treetable-tbody > tr:first-child > td {
border-top: 0 none;
}
.p-treetable-virtual-table {
position: absolute;
}
/* Frozen Columns */
.p-treetable-frozen-view .p-treetable-scrollable-body {
overflow: hidden;
}
.p-treetable-frozen-view > .p-treetable-scrollable-body > table > .p-treetable-tbody > tr > td:last-child {
border-right: 0 none;
}
.p-treetable-unfrozen-view {
position: absolute;
top: 0px;
}
/* Filter */
.p-column-filter {
width: 100%;
}
/* Resizable */
.p-treetable-resizable > .p-treetable-tablewrapper {
overflow-x: auto;
}
.p-treetable-resizable .p-treetable-thead > tr > th,
.p-treetable-resizable .p-treetable-tfoot > tr > td,
.p-treetable-resizable .p-treetable-tbody > tr > td {
overflow: hidden;
}
.p-treetable-resizable .p-resizable-column {
background-clip: padding-box;
position: relative;
}
.p-treetable-resizable-fit .p-resizable-column:last-child .p-column-resizer {
display: none;
}
.p-treetable .p-column-resizer {
display: block;
position: absolute !important;
top: 0;
right: 0;
margin: 0;
width: .5em;
height: 100%;
padding: 0px;
cursor:col-resize;
border: 1px solid transparent;
}
.p-treetable .p-column-resizer-helper {
width: 1px;
position: absolute;
z-index: 10;
display: none;
}
/* Selection */
.p-treetable .p-treetable-checkbox {
margin: 0 .5em 0 .25em;
vertical-align: middle;
}
/* Edit */
.p-treetable .p-treetable-tbody > tr > td.p-cell-editing .p-component {
width: 100%;
}
/* Reorder */
.p-treetable-reorder-indicator-up,
.p-treetable-reorder-indicator-down {
position: absolute;
display: none;
}
/* Responsive */
.p-treetable-responsive .p-treetable-tbody > tr > td .p-column-title {
display: none;
}
@media screen and (max-width: 40em) {
.p-treetable-responsive .p-treetable-thead > tr > th,
.p-treetable-responsive .p-treetable-tfoot > tr > td {
display: none !important;
}
.p-treetable-responsive .p-treetable-tbody > tr > td {
text-align: left;
display: block;
border: 0 none;
width: 100% !important;
float: left;
clear: left;
}
.p-treetable-responsive .p-treetable-tbody > tr > td .p-column-title {
padding: .4em;
min-width: 30%;
display: inline-block;
margin: -.4em 1em -.4em -.4em;
font-weight: bold;
}
}
/* Loader */
.p-treetable-loading-overlay {
position: absolute;
width: 100%;
height: 100%;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=10)";
opacity: 0.1;
z-index: 1;
}
.p-treetable-loading-content {
position: absolute;
left: 50%;
top: 50%;
z-index: 2;
margin-top: -1em;
margin-left: -1em;
}
.p-treetable .p-treetable-loading-icon {
font-size: 2em;
}
</style>

View File

@ -0,0 +1,26 @@
<script>
export default {
functional: true,
props: {
column: {
type: null,
default: null
},
data: {
type: null,
default: null
},
type: {
type: String,
default: null
}
},
render(createElement, context) {
const content = context.props.column.$scopedSlots[context.props.type]({
'data': context.props.data,
'column': context.props.column
});
return [content];
}
};
</script>

View File

@ -0,0 +1,96 @@
<template>
<tr :class="containerClass">
<td v-for="(col,i) of columns" :key="col.columnKey||col.field||i" :style="col.bodyStyle" :class="col.bodyClass">
<span class="p-treetable-toggler p-unselectable-text" @click="toggle" v-if="col.expander" :style="togglerStyle">
<i :class="togglerIcon"></i>
</span>
<TTColumnSlot :data="node" :column="col" type="body" v-if="col.$scopedSlots.body" />
<template v-else>{{resolveFieldData(node.data, col.field)}}</template>
</td>
</tr>
</template>
<script>
import ObjectUtils from '../utils/ObjectUtils';
import TreeTableColumnSlot from './TreeTableColumnSlot';
import { Fragment } from 'vue-fragment';
export default {
name: 'sub-ttnode',
props: {
node: {
type: null,
default: null
},
columns: {
type: null,
default: null
},
expandedKeys: {
type: null,
default: null
},
selectionKeys: {
type: null,
default: null
},
selectionMode: {
type: String,
default: null
},
level: {
type: Number,
default: 0
}
},
methods: {
resolveFieldData(rowData, field) {
return ObjectUtils.resolveFieldData(rowData, field);
},
toggle() {
this.$emit('node-toggle', this.node);
},
onChildNodeToggle(node) {
this.$emit('node-toggle', node);
}
},
computed: {
containerClass() {
return [this.node.styleClass, {
'p-highlight': this.selected
}]
},
hasChildren() {
return this.node.children && this.node.children.length > 0;
},
expanded() {
return this.expandedKeys && this.expandedKeys[this.node.key] === true;
},
leaf() {
return this.node.leaf === false ? false : !(this.node.children && this.node.children.length);
},
selected() {
return (this.selectionMode && this.selectionKeys) ? this.selectionKeys[this.node.key] === true : false;
},
togglerIcon() {
return ['p-treetable-toggler-icon pi pi-fw', {'pi-chevron-right': !this.expanded, 'pi-chevron-down': this.expanded}];
},
togglerStyle() {
return {
marginLeft: this.level * 16 + 'px',
visibility: this.leaf ? 'hidden' : 'visible'
};
},
childLevel() {
return this.level + 1;
},
childrenToRender() {
return this.expanded ? this.node.children: null;
}
},
components: {
'TTColumnSlot': TreeTableColumnSlot,
'TTFragment': Fragment
}
}
</script>

View File

@ -48,6 +48,7 @@ import TabView from './components/tabview/TabView';
import TabPanel from './components/tabpanel/TabPanel'; import TabPanel from './components/tabpanel/TabPanel';
import Textarea from './components/textarea/Textarea'; import Textarea from './components/textarea/Textarea';
import Tree from './components/tree/Tree'; import Tree from './components/tree/Tree';
import TreeTable from './components/treetable/TreeTable';
import Toast from './components/toast/Toast'; import Toast from './components/toast/Toast';
import ToastService from './components/toast/ToastService'; import ToastService from './components/toast/ToastService';
import Toolbar from './components/toolbar/Toolbar'; import Toolbar from './components/toolbar/Toolbar';
@ -119,6 +120,7 @@ Vue.component('Toast', Toast);
Vue.component('Toolbar', Toolbar); Vue.component('Toolbar', Toolbar);
Vue.component('ToggleButton', ToggleButton); Vue.component('ToggleButton', ToggleButton);
Vue.component('Tree', Tree); Vue.component('Tree', Tree);
Vue.component('TreeTable', TreeTable);
Vue.component('TriStateCheckbox', TriStateCheckbox); Vue.component('TriStateCheckbox', TriStateCheckbox);
Vue.component('ValidationMessage', ValidationMessage); Vue.component('ValidationMessage', ValidationMessage);

View File

@ -380,7 +380,12 @@ export default new Router({
path: '/tree/filter', path: '/tree/filter',
name: 'treefilter', name: 'treefilter',
component: () => import('./views/tree/TreeFilterDemo.vue') component: () => import('./views/tree/TreeFilterDemo.vue')
}, },
{
path: '/treetable',
name: 'treetable',
component: () => import('./views/treetable/TreeTableDemo.vue')
},
{ {
path: '/tristatecheckbox', path: '/tristatecheckbox',
name: 'tristatecheckbox', name: 'tristatecheckbox',

View File

@ -0,0 +1,75 @@
<template>
<div>
<TreeTableSubMenu />
<div class="content-section introduction">
<div class="feature-intro">
<h1>TreeTable</h1>
<p>TreeTable is used to display hierarchical data in tabular format.</p>
</div>
</div>
<div class="content-section implementation">
<h3>Basic</h3>
<TreeTable :value="nodes">
<Column field="name" header="Name" :expander="true"></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
</TreeTable>
</div>
<TreeTableDoc />
</div>
</template>
<script>
import NodeService from '../../service/NodeService';
import TreeTableDoc from './TreeTableDoc';
import TreeTableSubMenu from './TreeTableSubMenu';
export default {
data() {
return {
nodes: null,
expandedKeys: {}
}
},
nodeService: null,
created() {
this.nodeService = new NodeService();
},
mounted() {
this.nodeService.getTreeTableNodes().then(data => this.nodes = data);
},
methods: {
expandAll() {
for (let node of this.nodes) {
this.expandNode(node);
}
this.expandedKeys = {...this.expandedKeys};
},
collapseAll() {
this.expandedKeys = {};
},
expandNode(node) {
this.expandedKeys[node.key] = true;
if (node.children && node.children.length) {
for (let child of node.children) {
this.expandNode(child);
}
}
}
},
components: {
'TreeTableDoc': TreeTableDoc,
'TreeTableSubMenu': TreeTableSubMenu
}
}
</script>
<style scoped>
button {
margin-right: .5em;
}
</style>

View File

@ -0,0 +1,783 @@
<template>
<div class="content-section documentation">
<TabView>
<TabPanel header="Documentation">
<h3>Import</h3>
<CodeHighlight lang="javascript">
import Tree from 'primevue/tree';
</CodeHighlight>
<h3>Getting Started</h3>
<p>Tree component requires an array of TreeNode objects as its <i>value</i>.</p>
<h3>TreeNode API</h3>
<div class="doc-tablewrapper">
<table class="doc-table">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>key</td>
<td>any</td>
<td>null</td>
<td>Mandatory unique key of the node.</td>
</tr>
<tr>
<td>label</td>
<td>string</td>
<td>null</td>
<td>Label of the node.</td>
</tr>
<tr>
<td>data</td>
<td>any</td>
<td>null</td>
<td>Data represented by the node.</td>
</tr>
<tr>
<td>type</td>
<td>string</td>
<td>null</td>
<td>Type of the node to match a template.</td>
</tr>
<tr>
<td>icon</td>
<td>string</td>
<td>null</td>
<td>Icon of the node to display next to content.</td>
</tr>
<tr>
<td>children</td>
<td>TreeNode[]</td>
<td>null</td>
<td>An array of treenodes as children.</td>
</tr>
<tr>
<td>style</td>
<td>string</td>
<td>null</td>
<td>Inline style of the node.</td>
</tr>
<tr>
<td>styleClass</td>
<td>string</td>
<td>null</td>
<td>Style class of the node.</td>
</tr>
<tr>
<td>selectable</td>
<td>boolean</td>
<td>null</td>
<td>Whether the node is selectable when selection mode is enabled.</td>
</tr>
<tr>
<td>leaf</td>
<td>boolean</td>
<td>null</td>
<td>Specifies if the node has children. Used in lazy loading.</td>
</tr>
</tbody>
</table>
</div>
<p>Example below loads the tree nodes from a remote datasource via a service called NodeService.</p>
<CodeHighlight>
<template v-pre>
&lt;Tree :value="nodes"&gt;&lt;/Tree&gt;
</template>
</CodeHighlight>
<CodeHighlight lang="javascript">
import NodeService from '../../service/NodeService';
export default {
data() {
return {
nodes: null
}
},
nodeService: null,
created() {
this.nodeService = new NodeService();
},
mounted() {
this.nodeService.getTreeNodes().then(data => this.nodes = data);
}
}
</CodeHighlight>
<CodeHighlight lang="javascript">
import axios from 'axios';
export default class NodeService {
getTreeNodes() {
return axios.get('demo/data/treenodes.json').then(res => res.data.root);
}
}
</CodeHighlight>
<p>The json response sample would be as following.</p>
<CodeHighlight lang="javascript">
{
"root": [
{
"key": "0",
"label": "Documents",
"data": "Documents Folder",
"icon": "pi pi-fw pi-inbox",
"children": [{
"key": "0-0",
"label": "Work",
"data": "Work Folder",
"icon": "pi pi-fw pi-cog",
"children": [{ "key": "0-0-0", "label": "Expenses.doc", "icon": "pi pi-fw pi-file", "data": "Expenses Document" }, { "key": "0-0-1", "label": "Resume.doc", "icon": "pi pi-fw pi-file", "data": "Resume Document" }]
},
{
"key": "0-1",
"label": "Home",
"data": "Home Folder",
"icon": "pi pi-fw pi-home",
"children": [{ "key": "0-1-0", "label": "Invoices.txt", "icon": "pi pi-fw pi-file", "data": "Invoices for this month" }]
}]
},
{
"key": "1",
"label": "Events",
"data": "Events Folder",
"icon": "pi pi-fw pi-calendar",
"children": [
{ "key": "1-0", "label": "Meeting", "icon": "pi pi-fw pi-calendar-plus", "data": "Meeting" },
{ "key": "1-1", "label": "Product Launch", "icon": "pi pi-fw pi-calendar-plus", "data": "Product Launch" },
{ "key": "1-2", "label": "Report Review", "icon": "pi pi-fw pi-calendar-plus", "data": "Report Review" }]
},
{
"key": "2",
"label": "Movies",
"data": "Movies Folder",
"icon": "pi pi-fw pi-star",
"children": [{
"key": "2-0",
"icon": "pi pi-fw pi-star",
"label": "Al Pacino",
"data": "Pacino Movies",
"children": [{ "key": "2-0-0", "label": "Scarface", "icon": "pi pi-fw pi-video", "data": "Scarface Movie" }, { "key": "2-0-1", "label": "Serpico", "icon": "pi pi-fw pi-video", "data": "Serpico Movie" }]
},
{
"key": "2-1",
"label": "Robert De Niro",
"icon": "pi pi-fw pi-star",
"data": "De Niro Movies",
"children": [{ "key": "2-1-0", "label": "Goodfellas", "icon": "pi pi-fw pi-video", "data": "Goodfellas Movie" }, { "key": "2-1-1", "label": "Untouchables", "icon": "pi pi-fw pi-video", "data": "Untouchables Movie" }]
}]
}
]
}
</CodeHighlight>
<h3>Programmatic Control</h3>
<p>Tree state can be controlled programmatically with the <i>expandedKeys</i> property that defines the keys
that are expanded. This property is a Map instance whose key is the key of a node and value is a boolean. Note that <i>expandedKeys</i> also supports two-way binding with the sync modifier.
</p>
<p>Example below expands and collapses all nodes with buttons.</p>
<CodeHighlight>
<template v-pre>
&lt;div&gt;
&lt;Button type="button" icon="pi pi-plus" label="Expand All" @click="expandAll" /&gt;
&lt;Button type="button" icon="pi pi-minus" label="Collapse All" @click="collapseAll" /&gt;
&lt;/div&gt;
&lt;Tree :value="nodes" :expandedKeys="expandedKeys"&gt;&lt;/Tree&gt;
</template>
</CodeHighlight>
<CodeHighlight lang="javascript">
import NodeService from '../../service/NodeService';
export default {
data() {
return {
nodes: null,
expandedKeys: {}
}
},
nodeService: null,
created() {
this.nodeService = new NodeService();
},
mounted() {
this.nodeService.getTreeNodes().then(data => this.nodes = data);
},
methods: {
expandAll() {
for (let node of this.nodes) {
this.expandNode(node);
}
this.expandedKeys = {...this.expandedKeys};
},
collapseAll() {
this.expandedKeys = {};
},
expandNode(node) {
this.expandedKeys[node.key] = true;
if (node.children &amp;&amp; node.children.length) {
for (let child of node.children) {
this.expandNode(child);
}
}
}
}
}
</CodeHighlight>
<p>To display some nodes as expanded by default, simply add their keys to the map.</p>
<CodeHighlight lang="javascript">
import NodeService from '../../service/NodeService';
export default {
data() {
return {
nodes: null,
expandedKeys: {}
}
},
nodeService: null,
created() {
this.nodeService = new NodeService();
},
mounted() {
this.nodeService.getTreeNodes().then(data => {
this.nodes = data;
this.expandedKeys[this.nodes[0].key] = true;
this.expandedKeys[this.nodes[1].key] = true;
});
}
}
</CodeHighlight>
<h3>Selection</h3>
<p>Tree supports <b>single</b>, <b>multiple</b> and <b>checkbox</b> selection modes. Define the <i>selectionKeys</i> with the sync operator and the <i>selectionMode</i> properties to enable the selection.
By default in multiple selection mode, metaKey is necessary to add to existing selections however this can be configured with <i>metaKeySelection</i> property. Note that
in touch enabled devices, Tree does not require metaKey. In addition selection on a particular node can be disabled if the <i>selectable</i> is false on the node instance.</p>
<p>Similarly to the <i>expandedKeys</i>, <i>selectionKeys</i> is a Map instance whose key is the key of a node and value is a boolean in "single" and "multiple" cases. On the other hand
in "checkbox" mode, instead of a boolean, value should be an object that has "checked" and "partialChecked" properties to represent the checked state of a node.</p>
<CodeHighlight>
<template v-pre>
&lt;h3&gt;Single Selection&lt;/h3&gt;
&lt;Tree :value="nodes" selectionMode="single" :selectionKeys.sync="selectedKey1"&gt;&lt;/Tree&gt;
&lt;h3&gt;Multiple Selection with MetaKey&lt;/h3&gt;
&lt;Tree :value="nodes" selectionMode="multiple" :selectionKeys.sync="selectedKeys1"&gt;&lt;/Tree&gt;
&lt;h3&gt;Multiple Selection without MetaKey&lt;/h3&gt;
&lt;Tree :value="nodes" selectionMode="multiple" :selectionKeys.sync="selectedKeys2" :metaKeySelection="false"&gt;&lt;/Tree&gt;
&lt;h3&gt;Checkbox Selection&lt;/h3&gt;
&lt;Tree :value="nodes" selectionMode="checkbox" :selectionKeys.sync="selectedKeys3"&gt;&lt;/Tree&gt;
&lt;h3&gt;Events&lt;/h3&gt;
&lt;Tree :value="nodes" selectionMode="single" :selectionKeys.sync="selectedKey2" :metaKeySelection="false"
@node-select="onNodeSelect" @node-unselect="onNodeUnselect"&gt;&lt;/Tree&gt;
</template>
</CodeHighlight>
<CodeHighlight lang="javascript">
import NodeService from '../../service/NodeService';
export default {
data() {
return {
selectedKey1: null,
selectedKey2: null,
selectedKeys1: null,
selectedKeys2: null,
selectedKeys3: null,
nodes: null
}
},
nodeService: null,
created() {
this.nodeService = new NodeService();
},
mounted() {
this.nodeService.getTreeNodes().then(data => this.nodes = data);
},
methods: {
onNodeSelect(node) {
this.$toast.add({severity:'success', summary: 'Node Selected', detail: node.label, life: 3000});
},
onNodeUnselect(node) {
this.$toast.add({severity:'success', summary: 'Node Unselected', detail: node.label, life: 3000});
}
}
}
</CodeHighlight>
<p>To display some nodes as selected by default, simply add their keys to the map.</p>
<CodeHighlight lang="javascript">
import NodeService from '../../service/NodeService';
export default {
data() {
return {
selectedKey1: null,
selectedKey2: null,
selectedKeys1: null,
selectedKeys2: null,
selectedKeys3: null,
nodes: null
}
},
nodeService: null,
created() {
this.nodeService = new NodeService();
},
mounted() {
this.nodeService.getTreeNodes().then(data => {
this.nodes = data;
//single preselection
this.selectedKey1[this.nodes[0].key] = true;
//multiple preselection
this.selectedKeys2[this.nodes[0].key] = true;
this.selectedKeys2[this.nodes[1].key] = true;
//checkbox preselection
this.selectedKeys2[this.nodes[1].key] = {checked: true};
});
}
}
</CodeHighlight>
<h3>Lazy</h3>
<p>Lazy Loading is handy to deal with huge datasets. Idea is instead of loading the whole tree, load child nodes on demand
using expand expand. The important part is setting <i>leaf</i> to true on a node instance so that even without children,
tree would render an expand icon. Example below uses an in memory collection to mimic a lazy loading scenario with timeouts.
</p>
<CodeHighlight>
<template v-pre>
&lt;Tree :value="nodes" @node-expand="onNodeExpand" :loading="loading"&gt;&lt;/Tree&gt;
</template>
</CodeHighlight>
<CodeHighlight lang="javascript">
import NodeService from '../../service/NodeService';
export default {
data() {
return {
loading: false,
nodes: null
}
},
nodeService: null,
created() {
this.nodeService = new NodeService();
},
mounted() {
this.loading = true;
setTimeout(() => {
this.nodes = this.initateNodes();
this.loading = false;
}, 2000);
},
methods: {
onNodeExpand(node) {
if (!node.children) {
this.loading = true;
setTimeout(() => {
let _node = {...node};
_node.children = [];
for (let i = 0; i &lt; 3; i++) {
_node.children.push({
key: node.key + '-' + i,
label: 'Lazy ' + node.label + '-' + i
});
}
let _nodes = {...this.nodes}
_nodes[parseInt(node.key, 10)] = _node;
this.nodes = _nodes;
this.loading = false;
}, 500);
}
},
initateNodes() {
return [{
key: '0',
label: 'Node 0',
leaf: false
},
{
key: '1',
label: 'Node 1',
leaf: false
},
{
key: '2',
label: 'Node 2',
leaf: false
}];
}
}
}
</CodeHighlight>
<h3>Templating</h3>
<p>The <i>type</i> property of a TreeNode is used to map a template to a node to create the node label. If it is undefined and no default template is available,
label of the node is used.</p>
<CodeHighlight>
<template v-pre>
&lt;Tree :value="nodes"&gt;
&lt;template #default="slotProps"&gt;
&lt;b&gt;&#123;&#123;slotProps.node.label&#125;&#125;&lt;/b&gt;
&lt;/template&gt;
&lt;template #url="slotProps"&gt;
&lt;a :href="slotProps.node.data"&gt;&#123;&#123;slotProps.node.label&#125;&#125;&lt;/a&gt;
&lt;/template&gt;
&lt;/Tree&gt;
</template>
</CodeHighlight>
<CodeHighlight lang="javascript">
export default {
data() {
return {
nodes: [
{
key: '0',
label: 'Introduction',
children: [
{key: '0-0', label: 'What is Vue.js?', data:'https://vuejs.org/v2/guide/#What-is-Vue-js', type: 'url'},
{key: '0-1', label: 'Getting Started', data: 'https://vuejs.org/v2/guide/#Getting-Started', type: 'url'},
{key: '0-2', label: 'Declarative Rendering', data:'https://vuejs.org/v2/guide/#Declarative-Rendering', type: 'url'},
{key: '0-3', label: 'Conditionals and Loops', data: 'https://vuejs.org/v2/guide/#Conditionals-and-Loops', type: 'url'}
]
},
{
key: '1',
label: 'Components In-Depth',
children: [
{key: '1-0', label: 'Component Registration', data: 'https://vuejs.org/v2/guide/components-registration.html', type: 'url'},
{key: '1-1', llabel: 'Props', data: 'https://vuejs.org/v2/guide/components-props.html', type: 'url'},
{key: '1-2', llabel: 'Custom Events', data: 'https://vuejs.org/v2/guide/components-custom-events.html', type: 'url'},
{key: '1-3', llabel: 'Slots', data: 'https://vuejs.org/v2/guide/components-slots.html', type: 'url'}
]
}
]
}
}
}
</CodeHighlight>
<h3>Filtering</h3>
<p>Filtering is enabled by setting the <i>filter</i> property to true, by default label property of a node
is used to compare against the value in the text field, in order to customize which field(s) should be used during search, define the <i>filterBy</i> property as a comma separated list.</p>
<p>In addition <i>filterMode</i> specifies the filtering strategy. In <b>lenient</b> mode when the query matches a node, children of the node are not searched further as all descendants of the node are included. On the other hand,
in <b>strict</b> mode when the query matches a node, filtering continues on all descendants.</p>
<CodeHighlight>
<template v-pre>
&lt;h3&gt;Lenient Filter&lt;/h3&gt;
&lt;Tree :value="nodes" :filter="true" filterMode="lenient"&gt;&lt;/Tree&gt;
&lt;h3&gt;Strict Filter&lt;/h3&gt;
&lt;Tree :value="nodes" :filter="true" filterMode="strict"&gt;&lt;/Tree&gt;
</template>
</CodeHighlight>
<CodeHighlight lang="javascript">
import NodeService from '../../service/NodeService';
export default {
data() {
return {
nodes: null,
expandedKeys: {}
}
},
nodeService: null,
created() {
this.nodeService = new NodeService();
},
mounted() {
this.nodeService.getTreeNodes().then(data => this.nodes = data);
},
methods: {
expandAll() {
for (let node of this.nodes) {
this.expandNode(node);
}
this.expandedKeys = {...this.expandedKeys};
},
collapseAll() {
this.expandedKeys = {};
},
expandNode(node) {
this.expandedKeys[node.key] = true;
if (node.children &lt;&lt; node.children.length) {
for (let child of node.children) {
this.expandNode(child);
}
}
}
}
}
</CodeHighlight>
<h3>Properties</h3>
<p>Any valid attribute such as name and autofocus are passed to the underlying input element. Following is the additional property to configure the component.</p>
<div class="doc-tablewrapper">
<table class="doc-table">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>value</td>
<td>array</td>
<td>null</td>
<td>An array of treenodes.</td>
</tr>
<tr>
<td>expandedKeys</td>
<td>array</td>
<td>null</td>
<td>A map of keys to represent the state of the tree expansion state in controlled mode.</td>
</tr>
<tr>
<td>selectionMode</td>
<td>string</td>
<td>null</td>
<td>Defines the selection mode, valid values "single", "multiple", and "checkbox".</td>
</tr>
<tr>
<td>selectionKeys</td>
<td>any</td>
<td>null</td>
<td>A map of keys to control the selection state.</td>
</tr>
<tr>
<td>metaKeySelection</td>
<td>boolean</td>
<td>true</td>
<td>Defines how multiple items can be selected, when true metaKey needs to be pressed to select or unselect an item and when set to false selection of each item
can be toggled individually. On touch enabled devices, metaKeySelection is turned off automatically.</td>
</tr>
<tr>
<td>loading</td>
<td>boolean</td>
<td>false</td>
<td>Whether to display loading indicator.</td>
</tr>
<tr>
<td>loadingIcon</td>
<td>string</td>
<td>pi pi-spin</td>
<td>Icon to display when tree is loading.</td>
</tr>
<tr>
<td>filter</td>
<td>boolean</td>
<td>false</td>
<td>When specified, displays an input field to filter the items.</td>
</tr>
<tr>
<td>filterBy</td>
<td>string</td>
<td>label</td>
<td>When filtering is enabled, filterBy decides which field or fields (comma separated) to search against.</td>
</tr>
<tr>
<td>filterMode</td>
<td>string</td>
<td>lenient</td>
<td>Mode for filtering valid values are "lenient" and "strict". Default is lenient.</td>
</tr>
<tr>
<td>filterPlaceholder</td>
<td>string</td>
<td>null</td>
<td>Placeholder text to show when filter input is empty.</td>
</tr>
</tbody>
</table>
</div>
<h3>Events</h3>
<div class="doc-tablewrapper">
<table class="doc-table">
<thead>
<tr>
<th>Name</th>
<th>Parameters</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>node-select</td>
<td>node: Node instance</td>
<td>Callback to invoke when a node is selected.</td>
</tr>
<tr>
<td>node-unselect</td>
<td>node: Node instance</td>
<td>Callback to invoke when a node is unselected.</td>
</tr>
<tr>
<td>node-expand</td>
<td>node: Node instance</td>
<td>Callback to invoke when a node is expanded.</td>
</tr>
<tr>
<td>node-collapse</td>
<td>node: Node instance</td>
<td>Callback to invoke when a node is collapsed.</td>
</tr>
</tbody>
</table>
</div>
<h3>Styling</h3>
<p>Following is the list of structural style classes, for theming classes visit <router-link to="/theming">theming</router-link> page.</p>
<div class="doc-tablewrapper">
<table class="doc-table">
<thead>
<tr>
<th>Name</th>
<th>Element</th>
</tr>
</thead>
<tbody>
<tr>
<td>p-tree</td>
<td>Main container element</td>
</tr>
<tr>
<td>p-tree-horizontal</td>
<td>Main container element in horizontal mode</td>
</tr>
<tr>
<td>p-tree-container</td>
<td>Container of nodes</td>
</tr>
<tr>
<td>p-treenode</td>
<td>A treenode element</td>
</tr>
<tr>
<td>p-treenode-content</td>
<td>Content of a treenode</td>
</tr>
<tr>
<td>p-treenode-toggler</td>
<td>Toggle element</td>
</tr>
<tr>
<td>p-treenode-toggler-icon</td>
<td>Toggle icon</td>
</tr>
<tr>
<td>p-treenode-icon</td>
<td>Icon of a treenode</td>
</tr>
<tr>
<td>p-treenode-label</td>
<td>Label of a treenode</td>
</tr>
<tr>
<td>p-treenode-children</td>
<td>Container element for node children</td>
</tr>
</tbody>
</table>
</div>
<h3>Dependencies</h3>
<p>None.</p>
</TabPanel>
<TabPanel header="Source">
<a href="https://github.com/primefaces/primevue/tree/master/src/views/tree" class="btn-viewsource" target="_blank" rel="noopener noreferrer">
<span>View on GitHub</span>
</a>
<CodeHighlight>
<template v-pre>
&lt;h3&gt;Basic&lt;/h3&gt;
&lt;Tree :value="nodes"&gt;&lt;/Tree&gt;
&lt;h3&gt;Programmatic Control&lt;/h3&gt;
&lt;div style="margin-bottom: 1em"&gt;
&lt;Button type="button" icon="pi pi-plus" label="Expand All" @click="expandAll" /&gt;
&lt;Button type="button" icon="pi pi-minus" label="Collapse All" @click="collapseAll" /&gt;
&lt;/div&gt;
&lt;Tree :value="nodes" :expandedKeys="expandedKeys"&gt;&lt;/Tree&gt;
</template>
</CodeHighlight>
<CodeHighlight lang="javascript">
import NodeService from '../../service/NodeService';
export default {
data() {
return {
nodes: null,
expandedKeys: {}
}
},
nodeService: null,
created() {
this.nodeService = new NodeService();
},
mounted() {
this.nodeService.getTreeNodes().then(data => this.nodes = data);
},
methods: {
expandAll() {
for (let node of this.nodes) {
this.expandNode(node);
}
this.expandedKeys = {...this.expandedKeys};
},
collapseAll() {
this.expandedKeys = {};
},
expandNode(node) {
this.expandedKeys[node.key] = true;
if (node.children &amp;&amp; node.children.length) {
for (let child of node.children) {
this.expandNode(child);
}
}
}
}
}
</CodeHighlight>
</TabPanel>
</TabView>
</div>
</template>

View File

@ -0,0 +1,7 @@
<template>
<div class="content-section content-submenu p-clearfix">
<ul>
<li><router-link to="/treetable">&#9679; Documentation</router-link></li>
</ul>
</div>
</template>