Fixed #396 - Scrollable Tree

pull/1196/head^2
Cagatay Civici 2021-05-13 17:14:52 +03:00
parent 64df59f515
commit bf2e456705
6 changed files with 279 additions and 6 deletions

View File

@ -459,6 +459,10 @@
{
"name": "Filter",
"to": "/tree/filter"
},
{
"name": "Scroll",
"to": "/tree/scroll"
}
]
},

View File

@ -13,6 +13,7 @@ interface TreeProps {
filterMode?: string;
filterPlaceholder?: string;
filterLocale?: string;
scrollHeight?: string;
}
declare class Tree {

View File

@ -10,11 +10,13 @@
@keydown="onFilterKeydown" v-model="filterValue" />
<span class="p-tree-filter-icon pi pi-search"></span>
</div>
<ul class="p-tree-container" role="tree">
<TreeNode v-for="node of valueToRender" :key="node.key" :node="node" :templates="$slots"
:expandedKeys="d_expandedKeys" @node-toggle="onNodeToggle" @node-click="onNodeClick"
:selectionMode="selectionMode" :selectionKeys="selectionKeys" @checkbox-change="onCheckboxChange"></TreeNode>
</ul>
<div class="p-tree-wrapper" :style="{maxHeight: scrollHeight}">
<ul class="p-tree-container" role="tree">
<TreeNode v-for="node of valueToRender" :key="node.key" :node="node" :templates="$slots"
:expandedKeys="d_expandedKeys" @node-toggle="onNodeToggle" @node-click="onNodeClick"
:selectionMode="selectionMode" :selectionKeys="selectionKeys" @checkbox-change="onCheckboxChange"></TreeNode>
</ul>
</div>
</div>
</template>
@ -73,6 +75,10 @@ export default {
filterLocale: {
type: String,
default: undefined
},
scrollHeight: {
type: String,
default: null
}
},
data() {
@ -243,7 +249,8 @@ export default {
containerClass() {
return ['p-tree p-component', {
'p-tree-selectable': this.selectionMode != null,
'p-tree-loading': this.loading
'p-tree-loading': this.loading,
'p-tree-flex-scrollable': this.scrollHeight === 'flex'
}];
},
loadingIconClass() {
@ -294,6 +301,10 @@ export default {
list-style-type: none;
}
.p-tree-wrapper {
overflow: auto;
}
.p-treenode-selectable {
cursor: pointer;
user-select: none;
@ -346,4 +357,15 @@ export default {
align-items: 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>

View File

@ -706,6 +706,11 @@ const routes = [
name: 'treefilter',
component: () => import('../views/tree/TreeFilterDemo.vue')
},
{
path: '/tree/scroll',
name: 'treescroll',
component: () => import('../views/tree/TreeScrollDemo.vue')
},
{
path: '/treeselect',
name: 'treeselect',

View File

@ -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>
&lt;Tree :value="nodes1" scrollHeight="200px"&gt;&lt;/Tree&gt;
</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>
&lt;Button type="button" icon="pi pi-external-link" label="View" @click="dialogVisible = true"&gt;&lt;/Button&gt;
&lt;Dialog header="Flex Scroll" v-model:visible="dialogVisible" :style="{width: '50vw'}" maximizable
:contentStyle="{height: '300px'}" class="p-fluid"&gt;
&lt;Tree :value="nodes2" scrollHeight="flex"&gt;&lt;/Tree&gt;
&lt;template #footer&gt;
&lt;Button type="button" icon="pi pi-check" @click="dialogVisible = false" class="p-button-text"&gt;&lt;/Button&gt;
&lt;/template&gt;
&lt;/Dialog&gt;
</template>
</code></pre>
<h5>Filtering</h5>

View File

@ -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>