Fixed #396 - Scrollable Tree
parent
64df59f515
commit
bf2e456705
|
@ -459,6 +459,10 @@
|
||||||
{
|
{
|
||||||
"name": "Filter",
|
"name": "Filter",
|
||||||
"to": "/tree/filter"
|
"to": "/tree/filter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Scroll",
|
||||||
|
"to": "/tree/scroll"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,6 +13,7 @@ interface TreeProps {
|
||||||
filterMode?: string;
|
filterMode?: string;
|
||||||
filterPlaceholder?: string;
|
filterPlaceholder?: string;
|
||||||
filterLocale?: string;
|
filterLocale?: string;
|
||||||
|
scrollHeight?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare class Tree {
|
declare class Tree {
|
||||||
|
|
|
@ -10,11 +10,13 @@
|
||||||
@keydown="onFilterKeydown" v-model="filterValue" />
|
@keydown="onFilterKeydown" v-model="filterValue" />
|
||||||
<span class="p-tree-filter-icon pi pi-search"></span>
|
<span class="p-tree-filter-icon pi pi-search"></span>
|
||||||
</div>
|
</div>
|
||||||
<ul class="p-tree-container" role="tree">
|
<div class="p-tree-wrapper" :style="{maxHeight: scrollHeight}">
|
||||||
<TreeNode v-for="node of valueToRender" :key="node.key" :node="node" :templates="$slots"
|
<ul class="p-tree-container" role="tree">
|
||||||
:expandedKeys="d_expandedKeys" @node-toggle="onNodeToggle" @node-click="onNodeClick"
|
<TreeNode v-for="node of valueToRender" :key="node.key" :node="node" :templates="$slots"
|
||||||
:selectionMode="selectionMode" :selectionKeys="selectionKeys" @checkbox-change="onCheckboxChange"></TreeNode>
|
:expandedKeys="d_expandedKeys" @node-toggle="onNodeToggle" @node-click="onNodeClick"
|
||||||
</ul>
|
:selectionMode="selectionMode" :selectionKeys="selectionKeys" @checkbox-change="onCheckboxChange"></TreeNode>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -73,6 +75,10 @@ export default {
|
||||||
filterLocale: {
|
filterLocale: {
|
||||||
type: String,
|
type: String,
|
||||||
default: undefined
|
default: undefined
|
||||||
|
},
|
||||||
|
scrollHeight: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -243,7 +249,8 @@ export default {
|
||||||
containerClass() {
|
containerClass() {
|
||||||
return ['p-tree p-component', {
|
return ['p-tree p-component', {
|
||||||
'p-tree-selectable': this.selectionMode != null,
|
'p-tree-selectable': this.selectionMode != null,
|
||||||
'p-tree-loading': this.loading
|
'p-tree-loading': this.loading,
|
||||||
|
'p-tree-flex-scrollable': this.scrollHeight === 'flex'
|
||||||
}];
|
}];
|
||||||
},
|
},
|
||||||
loadingIconClass() {
|
loadingIconClass() {
|
||||||
|
@ -294,6 +301,10 @@ export default {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-tree-wrapper {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.p-treenode-selectable {
|
.p-treenode-selectable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
@ -346,4 +357,15 @@ export default {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-tree-flex-scrollable {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-tree-flex-scrollable .p-tree-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -706,6 +706,11 @@ const routes = [
|
||||||
name: 'treefilter',
|
name: 'treefilter',
|
||||||
component: () => import('../views/tree/TreeFilterDemo.vue')
|
component: () => import('../views/tree/TreeFilterDemo.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/tree/scroll',
|
||||||
|
name: 'treescroll',
|
||||||
|
component: () => import('../views/tree/TreeScrollDemo.vue')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/treeselect',
|
path: '/treeselect',
|
||||||
name: 'treeselect',
|
name: 'treeselect',
|
||||||
|
|
|
@ -486,6 +486,30 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<h5>Scrolling</h5>
|
||||||
|
<p>Scrolling is used to preserve space as content of the tree is dynamic and enabled by <i>scrollHeight</i> property.</p>
|
||||||
|
<pre v-code><code><template v-pre>
|
||||||
|
<Tree :value="nodes1" scrollHeight="200px"></Tree>
|
||||||
|
</template>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<h5>Flex Scroll</h5>
|
||||||
|
<p>In cases where viewport should adjust itself according to the table parent's height instead of a fixed viewport height, set <i>scrollHeight</i> option as <b>flex</b>. In example below,
|
||||||
|
table is inside a Dialog where viewport size dynamically responds to the dialog size changes such as maximizing.</p>
|
||||||
|
|
||||||
|
<pre v-code><code><template v-pre>
|
||||||
|
<Button type="button" icon="pi pi-external-link" label="View" @click="dialogVisible = true"></Button>
|
||||||
|
|
||||||
|
<Dialog header="Flex Scroll" v-model:visible="dialogVisible" :style="{width: '50vw'}" maximizable
|
||||||
|
:contentStyle="{height: '300px'}" class="p-fluid">
|
||||||
|
<Tree :value="nodes2" scrollHeight="flex"></Tree>
|
||||||
|
<template #footer>
|
||||||
|
<Button type="button" icon="pi pi-check" @click="dialogVisible = false" class="p-button-text"></Button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
||||||
<h5>Filtering</h5>
|
<h5>Filtering</h5>
|
||||||
|
|
|
@ -0,0 +1,217 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="content-section introduction">
|
||||||
|
<div class="feature-intro">
|
||||||
|
<h1>Tree <span>Scroll</span></h1>
|
||||||
|
<p>Scrolling is used to preserve space in cases when rendering large data.</p>
|
||||||
|
</div>
|
||||||
|
<AppDemoActions />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-section implementation">
|
||||||
|
<div class="card">
|
||||||
|
<h5>Regular Scroll</h5>
|
||||||
|
<p>Scrollable viewport is fixed.</p>
|
||||||
|
<Tree :value="nodes1" scrollHeight="200px"></Tree>
|
||||||
|
|
||||||
|
<h5>Flex Scroll</h5>
|
||||||
|
<p>Flex scroll feature makes the scrollable viewport section dynamic so that it can grow or shrink relative to the parent size of the tree. Click the button below
|
||||||
|
to display maximizable Dialog where data viewport adjusts itself according to the size changes.</p>
|
||||||
|
<Button type="button" icon="pi pi-external-link" label="View" @click="dialogVisible = true"></Button>
|
||||||
|
|
||||||
|
<Dialog header="Flex Scroll" v-model:visible="dialogVisible" :style="{width: '50vw'}" maximizable
|
||||||
|
:contentStyle="{height: '300px'}" class="p-fluid">
|
||||||
|
<Tree :value="nodes2" scrollHeight="flex"></Tree>
|
||||||
|
<template #footer>
|
||||||
|
<Button type="button" icon="pi pi-check" @click="dialogVisible = false" class="p-button-text"></Button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AppDoc name="TreeScrollDemo" :sources="sources" :service="['NodeService']" :data="['treenodes']" github="tree/TreeScrollDemo.vue" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import NodeService from '../../service/NodeService';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
nodes1: null,
|
||||||
|
nodes2: null,
|
||||||
|
dialogVisible: false,
|
||||||
|
sources: {
|
||||||
|
'options-api': {
|
||||||
|
tabName: 'Options API Source',
|
||||||
|
content: `
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h5>Regular Scroll</h5>
|
||||||
|
<p>Scrollable viewport is fixed.</p>
|
||||||
|
<Tree :value="nodes1" scrollHeight="200px"></Tree>
|
||||||
|
|
||||||
|
<h5>Flex Scroll</h5>
|
||||||
|
<p>Flex scroll feature makes the scrollable viewport section dynamic so that it can grow or shrink relative to the parent size of the tree. Click the button below
|
||||||
|
to display maximizable Dialog where data viewport adjusts itself according to the size changes.</p>
|
||||||
|
<Button type="button" icon="pi pi-external-link" label="View" @click="dialogVisible = true"></Button>
|
||||||
|
|
||||||
|
<Dialog header="Flex Scroll" v-model:visible="dialogVisible" :style="{width: '50vw'}" maximizable
|
||||||
|
:contentStyle="{height: '300px'}" class="p-fluid">
|
||||||
|
<Tree :value="nodes2" scrollHeight="flex"></Tree>
|
||||||
|
<template #footer>
|
||||||
|
<Button type="button" icon="pi pi-check" @click="dialogVisible = false" class="p-button-text"></Button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import NodeService from './service/NodeService';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
nodes1: null,
|
||||||
|
nodes2: null,
|
||||||
|
dialogVisible: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nodeService: null,
|
||||||
|
created() {
|
||||||
|
this.nodeService = new NodeService();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.nodeService.getTreeNodes().then(data => this.nodes1 = data);
|
||||||
|
this.nodes2 = Array.from({length: 100}).map((_,i) => this.createNode(i, 2));
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
createNode(i, children) {
|
||||||
|
let node = {
|
||||||
|
key: 'node_' + i,
|
||||||
|
label: 'Node ' + i,
|
||||||
|
data: 'Node ' + i,
|
||||||
|
expandedIcon: 'pi pi-folder-open',
|
||||||
|
collapsedIcon: 'pi pi-folder',
|
||||||
|
children: Array.from({length: children}).map((_,j) => {
|
||||||
|
return {
|
||||||
|
label: 'Node ' + i + '.' + j,
|
||||||
|
data: 'Node ' + i + '.' + j,
|
||||||
|
icon: 'pi pi-file-o'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<\\/script>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
'composition-api': {
|
||||||
|
tabName: 'Composition API Source',
|
||||||
|
content: `
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h5>Regular Scroll</h5>
|
||||||
|
<p>Scrollable viewport is fixed.</p>
|
||||||
|
<Tree :value="nodes1" scrollHeight="200px"></Tree>
|
||||||
|
|
||||||
|
<h5>Flex Scroll</h5>
|
||||||
|
<p>Flex scroll feature makes the scrollable viewport section dynamic so that it can grow or shrink relative to the parent size of the tree. Click the button below
|
||||||
|
to display maximizable Dialog where data viewport adjusts itself according to the size changes.</p>
|
||||||
|
<Button type="button" icon="pi pi-external-link" label="View" @click="dialogVisible = true"></Button>
|
||||||
|
|
||||||
|
<Dialog header="Flex Scroll" v-model:visible="dialogVisible" :style="{width: '50vw'}" :maximizable="true"
|
||||||
|
:contentStyle="{height: '300px'}" class="p-fluid">
|
||||||
|
<Tree :value="nodes2" scrollHeight="flex"></Tree>
|
||||||
|
<template #footer>
|
||||||
|
<Button type="button" icon="pi pi-check" @click="dialogVisible = false" class="p-button-text"></Button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { useToast } from 'primevue/usetoast';
|
||||||
|
import NodeService from './service/NodeService';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
const createNode = (i, children) => {
|
||||||
|
let node = {
|
||||||
|
key: 'node_' + i,
|
||||||
|
label: 'Node ' + i,
|
||||||
|
data: 'Node ' + i,
|
||||||
|
expandedIcon: 'pi pi-folder-open',
|
||||||
|
collapsedIcon: 'pi pi-folder',
|
||||||
|
children: Array.from({length: children}).map((_,j) => {
|
||||||
|
return {
|
||||||
|
label: 'Node ' + i + '.' + j,
|
||||||
|
data: 'Node ' + i + '.' + j,
|
||||||
|
icon: 'pi pi-file-o'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nodeService.value.getTreeNodes().then(data => nodes.value = data);
|
||||||
|
nodes2.value = Array.from({length: 100}).map((_,i) => createNode(i, 2));
|
||||||
|
})
|
||||||
|
|
||||||
|
const nodes1 = ref(null);
|
||||||
|
const nodes2 = ref(null);
|
||||||
|
const dialogVisible = ref(false);
|
||||||
|
const nodeService = ref(new NodeService());
|
||||||
|
|
||||||
|
return { nodes1, nodes2, dialogVisible }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<\\/script>`
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nodeService: null,
|
||||||
|
created() {
|
||||||
|
this.nodeService = new NodeService();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.nodeService.getTreeNodes().then(data => this.nodes1 = data);
|
||||||
|
this.nodes2 = Array.from({length: 100}).map((_,i) => this.createNode(i, 2));
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
createNode(i, children) {
|
||||||
|
let node = {
|
||||||
|
key: 'node_' + i,
|
||||||
|
label: 'Node ' + i,
|
||||||
|
data: 'Node ' + i,
|
||||||
|
expandedIcon: 'pi pi-folder-open',
|
||||||
|
collapsedIcon: 'pi pi-folder',
|
||||||
|
children: Array.from({length: children}).map((_,j) => {
|
||||||
|
return {
|
||||||
|
label: 'Node ' + i + '.' + j,
|
||||||
|
data: 'Node ' + i + '.' + j,
|
||||||
|
icon: 'pi pi-file-o'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
button {
|
||||||
|
margin-right: .5rem;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue