Backend API
<template>
<div>
<DxTreeList
id="treeList"
:data-source="dataSource"
:word-wrap-enabled="true"
:show-borders="true"
:focused-row-enabled="true"
v-model:focused-row-key="focusedRowKey"
parent-id-expr="Task_Parent_ID"
has-items-expr="Has_Items"
@focused-row-changed="onFocusedRowChanged"
>
<DxColumn
data-field="Task_ID"
width="100"
alignment="left"
/>
<DxColumn
:min-width="120"
data-field="Task_Assigned_Employee_ID"
caption="Assigned"
>
<DxLookup
:data-source="taskEmployees"
value-expr="ID"
display-expr="Name"
/>
</DxColumn>
<DxColumn
data-field="Task_Status"
caption="Status"
width="160"
/>
<DxColumn
data-field="Task_Start_Date"
caption="Start Date"
data-type="date"
width="160"
/>
<DxColumn
data-field="Task_Due_Date"
caption="Due Date"
data-type="date"
width="160"
/>
</DxTreeList>
<div class="task-info">
<div class="info">
<div class="task-subject">{{ taskSubject }}</div>
<span class="task-assigned">{{ taskAssigned }}</span>
<span class="start-date">{{ startDate }}</span>
</div>
<div class="progress">
<span class="task-status">{{ taskStatus }}</span>
<span class="task-progress">{{ taskProgress }}</span>
</div>
</div>
<div class="options">
<div class="caption">Options</div>
<div class="option">
<span>Focused row key </span>
<DxNumberBox
id="taskId"
:min="1"
:max="182"
:step="0"
v-model:value="focusedRowKey"
:input-attr="{ 'aria-label': 'Focused Row Key' }"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import {
DxTreeList, DxColumn, DxLookup, type DxTreeListTypes,
} from 'devextreme-vue/tree-list';
import DxNumberBox from 'devextreme-vue/number-box';
import { type CustomStore } from 'devextreme-vue/common/data';
import { createStore } from 'devextreme-aspnet-data-nojquery';
const url = 'https://js.devexpress.com/Demos/NetCore/api/TreeListTasks';
const taskSubject = ref('');
const taskAssigned = ref('');
const startDate = ref('');
const taskStatus = ref('');
const taskProgress = ref('');
const dataSource = createStore({
key: 'Task_ID',
loadUrl: `${url}/Tasks`,
onBeforeSend(_, ajaxOptions) {
ajaxOptions.xhrFields = { withCredentials: true };
},
});
const taskEmployees = createStore({
key: 'ID',
loadMode: 'raw',
loadUrl: `${url}/TaskEmployees`,
});
const focusedRowKey = ref(45);
function onFocusedRowChanged(e: DxTreeListTypes.FocusedRowChangedEvent) {
const rowData = e.row && e.row.data;
let cellValue;
let assigned: string = '';
if (rowData) {
cellValue = e.component.cellValue(e.row.rowIndex, 'Assigned');
taskEmployees.byKey(cellValue).done((item: Record<string, unknown>) => {
assigned = item.Name as string;
});
taskSubject.value = rowData.Task_Subject;
taskAssigned.value = assigned;
startDate.value = new Date(rowData.Task_Start_Date).toLocaleDateString();
taskStatus.value = rowData.Task_Status;
taskProgress.value = rowData.Task_Completion
? `${rowData.Task_Completion}%`
: '';
}
}
</script>
window.exports = window.exports || {};
window.config = {
transpiler: 'plugin-babel',
meta: {
'*.vue': {
loader: 'vue-loader',
},
'*.ts': {
loader: 'demo-ts-loader',
},
'*.svg': {
loader: 'svg-loader',
},
'devextreme/time_zone_utils.js': {
'esModule': true,
},
'devextreme/localization.js': {
'esModule': true,
},
'devextreme/viz/palette.js': {
'esModule': true,
},
'devextreme-aspnet-data-nojquery': {
'esModule': true,
},
'openai': {
'esModule': true,
},
},
paths: {
'project:': '../../../../',
'npm:': 'https://cdn.jsdelivr.net/npm/',
'bundles:': '../../../../bundles/',
'externals:': '../../../../bundles/externals/',
},
map: {
'vue': 'npm:vue@3.4.27/dist/vue.esm-browser.js',
'@vue/shared': 'npm:@vue/shared@3.4.27/dist/shared.cjs.prod.js',
'vue-loader': 'npm:dx-systemjs-vue-browser@1.1.2/index.js',
'demo-ts-loader': 'project:utils/demo-ts-loader.js',
'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js',
'svg-loader': 'project:utils/svg-loader.js',
'devextreme-aspnet-data-nojquery': 'npm:devextreme-aspnet-data-nojquery@5.0.0/index.js',
'mitt': 'npm:mitt/dist/mitt.umd.js',
'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js',
'luxon': 'npm:luxon@3.4.4/build/global/luxon.min.js',
'es6-object-assign': 'npm:es6-object-assign',
'devextreme': 'npm:devextreme@link:../../packages/devextreme/artifacts/npm/devextreme/cjs',
'devextreme-vue': 'npm:devextreme-vue@link:../../packages/devextreme-vue/npm/cjs',
'devextreme-quill': 'npm:devextreme-quill@1.7.6/dist/dx-quill.min.js',
'devexpress-diagram': 'npm:devexpress-diagram@2.2.24/dist/dx-diagram.js',
'devexpress-gantt': 'npm:devexpress-gantt@4.1.64/dist/dx-gantt.js',
'inferno': 'npm:inferno@8.2.3/dist/inferno.min.js',
'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js',
'inferno-create-element': 'npm:inferno-create-element@8.2.3/dist/inferno-create-element.min.js',
'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js',
'inferno-hydrate': 'npm:inferno-hydrate/dist/inferno-hydrate.min.js',
'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js',
'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js',
'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js',
'@preact/signals-core': 'npm:@preact/signals-core@1.8.0/dist/signals-core.min.js',
'plugin-babel': 'npm:systemjs-plugin-babel@0.0.25/plugin-babel.js',
'systemjs-babel-build': 'npm:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js',
// Prettier
'prettier/standalone': 'npm:prettier@2.8.8/standalone.js',
'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js',
},
packages: {
'devextreme-vue': {
main: 'index.js',
},
'devextreme-vue/common': {
main: 'index.js',
},
'devextreme': {
defaultExtension: 'js',
},
'devextreme/events/utils': {
main: 'index',
},
'devextreme/common/core/events/utils': {
main: 'index',
},
'devextreme/events': {
main: 'index',
},
'es6-object-assign': {
main: './index.js',
defaultExtension: 'js',
},
},
packageConfigPaths: [
'npm:@devextreme/*/package.json',
],
babelOptions: {
sourceMaps: false,
stage0: true,
},
};
System.config(window.config);
// eslint-disable-next-line
const useTgzInCSB = ['openai'];
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
#treeList {
max-height: 400px;
}
.task-info {
font-family: "Segoe UI";
min-height: 100px;
display: flex;
flex-wrap: nowrap;
border: 2px solid rgba(0, 0, 0, 0.1);
padding: 16px;
box-sizing: border-box;
align-items: center;
justify-content: space-between;
}
.info {
display: flex;
flex-direction: column;
}
.task-subject {
line-height: 29px;
font-size: 18px;
font-weight: bold;
}
.progress {
display: flex;
flex-direction: column;
white-space: pre;
min-width: 105px;
}
.task-progress {
line-height: 42px;
font-size: 40px;
font-weight: bold;
}
.options {
margin-top: 20px;
padding: 20px;
background-color: rgba(191, 191, 191, 0.15);
position: relative;
}
.caption {
font-size: 18px;
font-weight: 500;
}
.option {
margin-top: 10px;
}
.option > .dx-numberbox {
width: 200px;
display: inline-block;
vertical-align: middle;
}
.option > span {
margin-right: 10px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>DevExtreme Demo</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
<link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/25.1.6/css/dx.light.css" />
<link rel="stylesheet" type="text/css" href="styles.css" />
<script src="https://cdn.jsdelivr.net/npm/typescript@5.4.5/lib/typescript.js"></script>
<script type="module">
import * as vueCompilerSFC from "https://cdn.jsdelivr.net/npm/@vue/compiler-sfc@3.4.27/dist/compiler-sfc.esm-browser.js";
window.vueCompilerSFC = vueCompilerSFC;
</script>
<script src="https://cdn.jsdelivr.net/npm/core-js@2.6.12/client/shim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/systemjs@0.21.3/dist/system.js"></script>
<script type="text/javascript" src="config.js"></script>
<script type="text/javascript">
System.import("./index.ts");
</script>
</head>
<body class="dx-viewport">
<div class="demo-container">
<div id="app"></div>
</div>
</body>
</html>
In this demo, the row with the 45 key is focused initially. You can specify the focusedRowKey property via the input field below the TreeList. The onFocusedRowChanged function is used to display additional information below the component when the focused row changes.