Skip to content

Commit 0e6fbd1

Browse files
Vue: Composition API migration with TypeScript and cascading dropdown fixes
- Migrated from Options API to Vue 3 Composition API with <script setup lang="ts"> - Restructured App.vue to standard Vite template with RouterView - Moved DataGrid implementation to HomeContent.vue component - Created TypeScript data.ts with Employee, State, City interfaces - Fixed critical cascading dropdown issues: * Added onInitNewRow handler to initialize StateID/CityID arrays * Fixed undefined value handling in dropdown components * Removed auto-updating watchers that caused premature dropdown closure * City dropdown now shows all cities when no state is selected - Enhanced TypeScript implementation with DevExtreme Vue types: * Used DxDataGridTypes for proper event and template typing * Added proper DevExtreme Vue component references and imports * Used framework-specific imports from devextreme-vue packages - Applied Vue 3 best practices: * Composition API with reactive refs and computed properties * Proper watch functions for prop synchronization * Type-safe component props and event handlers - Fixed all ESLint issues and code formatting - Maintained cascading dropdown functionality with Apply button pattern - Removed all orig_ files as per migration guidelines
1 parent 920f9df commit 0e6fbd1

15 files changed

+428
-520
lines changed

Vue/package-lock.json

Lines changed: 0 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Vue/public/orig_index.html

Lines changed: 0 additions & 17 deletions
This file was deleted.

Vue/src/App.vue

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { RouterView } from 'vue-router';
33
</script>
44

55
<template>
6-
<div class="main">
7-
<RouterView/>
8-
</div>
6+
<RouterView/>
97
</template>
8+
9+
<style scoped>
10+
</style>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<template>
2+
<DxDropDownBox
3+
:data-source="dataSource"
4+
v-model:value="currentValue"
5+
display-expr="Name"
6+
value-expr="ID"
7+
content-template="contentTemplate"
8+
>
9+
<template #contentTemplate="{ data }">
10+
<div>
11+
<DxDataGrid
12+
:data-source="dataSource"
13+
:selected-row-keys="currentValue"
14+
:hover-state-enabled="true"
15+
:height="250"
16+
@initialized="onInitialized"
17+
>
18+
<DxColumn data-field="ID"/>
19+
<DxColumn data-field="Name"/>
20+
<DxSelection mode="multiple"/>
21+
</DxDataGrid>
22+
<DropDownSaveBtnComponent
23+
:data-grid="dataGrid"
24+
:cell-info="cellInfo"
25+
:drop-down-box="data.component"
26+
/>
27+
</div>
28+
</template>
29+
</DxDropDownBox>
30+
</template>
31+
32+
<script setup lang="ts">
33+
import { ref, watch, type Ref } from 'vue';
34+
import { DxDataGrid, DxSelection, DxColumn, type DxDataGridTypes } from 'devextreme-vue/data-grid';
35+
import DxDropDownBox from 'devextreme-vue/drop-down-box';
36+
import DropDownSaveBtnComponent from './DropDownSaveBtnComponent.vue';
37+
import type { Employee } from '@/data';
38+
import type { DataSource } from 'devextreme-vue/common/data';
39+
40+
interface Props {
41+
value: number[] | undefined;
42+
// eslint-disable-next-line no-unused-vars
43+
onValueChanged: (value: number[]) => void;
44+
dataSource: DataSource;
45+
cellInfo: DxDataGridTypes.ColumnEditCellTemplateData<Employee, number>;
46+
}
47+
48+
const props = defineProps<Props>();
49+
50+
const currentValue: Ref<number[]> = ref([...(props.value || [])]);
51+
const dataGrid = ref();
52+
53+
const onInitialized = (e: DxDataGridTypes.InitializedEvent): void => {
54+
dataGrid.value = e.component;
55+
};
56+
57+
watch(() => props.value, (newValue: number[] | undefined) => {
58+
currentValue.value = [...(newValue || [])];
59+
}, { deep: true });
60+
61+
</script>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<template>
2+
<DxButton
3+
text="Apply"
4+
@click="onClick"
5+
/>
6+
</template>
7+
8+
<script setup lang="ts">
9+
import type { Employee } from '@/data';
10+
import DxButton from 'devextreme-vue/button';
11+
import type { DxDataGridTypes } from 'devextreme-vue/cjs/data-grid';
12+
import type dxDataGrid from 'devextreme/ui/data_grid';
13+
import type dxDropDownBox from 'devextreme/ui/drop_down_box';
14+
15+
interface Props {
16+
dataGrid?: dxDataGrid;
17+
cellInfo: DxDataGridTypes.ColumnEditCellTemplateData<Employee, number>;
18+
dropDownBox?: dxDropDownBox;
19+
}
20+
21+
const props = defineProps<Props>();
22+
23+
const onClick = (): void => {
24+
if (!props.dataGrid) return;
25+
26+
const selectedKeys = props.dataGrid.getSelectedRowKeys() as number[];
27+
props.cellInfo.setValue(selectedKeys);
28+
props.dropDownBox?.option('value', selectedKeys);
29+
props.dropDownBox?.close();
30+
};
31+
</script>

Vue/src/components/HomeContent.vue

Lines changed: 130 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,133 @@
1-
<script setup lang="ts">
2-
import { computed, ref } from 'vue';
3-
4-
import 'devextreme/dist/css/dx.material.blue.light.compact.css';
5-
import DxButton from 'devextreme-vue/button';
6-
7-
const props = defineProps({
8-
text: {
9-
type: String,
10-
default: 'count',
11-
},
12-
});
13-
const count = ref(0);
14-
const buttonText = computed<string>(
15-
() => `Click ${props.text}: ${count.value}`
16-
);
17-
function clickHandler() {
18-
count.value += 1;
19-
}
20-
</script>
211
<template>
22-
<div>
23-
<DxButton
24-
:text="buttonText"
25-
@click="clickHandler"
26-
/>
2+
<div id="data-grid-demo">
3+
<DxDataGrid
4+
:data-source="data"
5+
@init-new-row="onInitNewRow"
6+
>
7+
<DxEditing
8+
:allow-updating="true"
9+
:allow-adding="true"
10+
mode="row"
11+
/>
12+
<DxColumn
13+
:set-cell-value="setStateValue"
14+
data-field="StateID"
15+
caption="State"
16+
:cell-template="arrayCellTemplate"
17+
edit-cell-template="multipleDropDownBoxStateTemplate"
18+
>
19+
<DxLookup
20+
:data-source="states"
21+
display-expr="Name"
22+
value-expr="ID"
23+
/>
24+
</DxColumn>
25+
<DxColumn
26+
data-field="CityID"
27+
caption="City"
28+
:cell-template="arrayCellTemplate"
29+
edit-cell-template="multipleDropDownBoxCityTemplate"
30+
>
31+
<DxLookup
32+
:data-source="getFilteredCities"
33+
display-expr="Name"
34+
value-expr="ID"
35+
/>
36+
</DxColumn>
37+
<template #multipleDropDownBoxStateTemplate="{ data: cellInfo }">
38+
<StateDropDownBoxComponent
39+
:value="cellInfo.value"
40+
:on-value-changed="cellInfo.setValue"
41+
:data-source="states"
42+
:cell-info="cellInfo"
43+
/>
44+
</template>
45+
<template #multipleDropDownBoxCityTemplate="{ data: cellInfo }">
46+
<CityDropDownBoxComponent
47+
:value="cellInfo.value"
48+
:on-value-changed="cellInfo.setValue"
49+
:data-source="cityDataSource(cellInfo)"
50+
:cell-info="cellInfo"
51+
/>
52+
</template>
53+
</DxDataGrid>
2754
</div>
2855
</template>
56+
57+
<script setup lang="ts">
58+
import 'devextreme/dist/css/dx.material.blue.light.compact.css';
59+
import {
60+
DxDataGrid,
61+
DxColumn,
62+
DxEditing,
63+
DxLookup,
64+
type DxDataGridTypes,
65+
} from 'devextreme-vue/data-grid';
66+
import service, { type City, type Employee } from '../data';
67+
import StateDropDownBoxComponent from './StateDropDownBoxComponent.vue';
68+
import CityDropDownBoxComponent from './CityDropDownBoxComponent.vue';
69+
import { DataSource, ArrayStore } from 'devextreme-vue/common/data';
70+
71+
const data: Employee[] = service.getEmployees();
72+
const states = service.getStates();
73+
const cities = service.getCities();
74+
75+
const onInitNewRow = (e: DxDataGridTypes.InitNewRowEvent<Employee, number>): void => {
76+
e.data.StateID = [];
77+
e.data.CityID = [];
78+
};
79+
80+
const setStateValue = async function(
81+
this: DxDataGridTypes.Column<Employee, number>,
82+
rowData: Employee,
83+
value: number[],
84+
currentRowData: Employee
85+
) {
86+
rowData.CityID = [];
87+
await this.defaultSetCellValue?.(rowData, value, currentRowData);
88+
};
89+
90+
const arrayCellTemplate = (
91+
container: HTMLElement,
92+
options: DxDataGridTypes.ColumnCellTemplateData<Employee, number>
93+
): void => {
94+
const noBreakSpace = '\u00A0';
95+
const text = (options.value || [])
96+
.map((element: number) => {
97+
return options.column.lookup?.calculateCellValue?.(element);
98+
})
99+
.join(', ');
100+
container.textContent = text || noBreakSpace;
101+
container.title = text;
102+
};
103+
104+
const getFilteredCities = (options: { data: Employee, key: number }): {
105+
store: City[];
106+
filter: [string, string, number[]] | null;
107+
} => {
108+
return {
109+
store: cities,
110+
filter: options.data ? ['StateID', '=', options.data.StateID] : null,
111+
};
112+
};
113+
114+
const cityDataSource = (cellInfo: any): DataSource => {
115+
return new DataSource({
116+
store: new ArrayStore({
117+
data: cities,
118+
key: 'ID',
119+
}),
120+
filter: (data: City) => {
121+
return (cellInfo.row.data?.StateID?.length > 0
122+
? cellInfo.row.data.StateID.includes(data.StateID)
123+
: true);
124+
},
125+
});
126+
};
127+
</script>
128+
129+
<style>
130+
#data-grid-demo {
131+
min-height: 700px;
132+
}
133+
</style>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<template>
2+
<DxDropDownBox
3+
:data-source="dataSource"
4+
v-model:value="currentValue"
5+
display-expr="Name"
6+
value-expr="ID"
7+
content-template="contentTemplate"
8+
>
9+
<template #contentTemplate="{ data }">
10+
<div>
11+
<DxDataGrid
12+
:data-source="dataSource"
13+
key-expr="ID"
14+
:selected-row-keys="currentValue"
15+
:hover-state-enabled="true"
16+
:height="250"
17+
@initialized="onInitialized"
18+
>
19+
<DxColumn data-field="ID"/>
20+
<DxColumn data-field="Name"/>
21+
<DxSelection mode="multiple"/>
22+
</DxDataGrid>
23+
<DropDownSaveBtnComponent
24+
:data-grid="dataGrid"
25+
:cell-info="cellInfo"
26+
:drop-down-box="data.component"
27+
/>
28+
</div>
29+
</template>
30+
</DxDropDownBox>
31+
</template>
32+
33+
<script setup lang="ts">
34+
import { ref, watch, type Ref } from 'vue';
35+
import { DxDataGrid, DxSelection, DxColumn, type DxDataGridTypes } from 'devextreme-vue/data-grid';
36+
import DxDropDownBox from 'devextreme-vue/drop-down-box';
37+
import DropDownSaveBtnComponent from './DropDownSaveBtnComponent.vue';
38+
import type { Employee, State } from '../data';
39+
40+
interface Props {
41+
value: number[] | undefined;
42+
// eslint-disable-next-line no-unused-vars
43+
onValueChanged: (value: number[]) => void;
44+
dataSource: State[];
45+
cellInfo: DxDataGridTypes.ColumnEditCellTemplateData<Employee, number>;
46+
}
47+
48+
const props = defineProps<Props>();
49+
50+
const currentValue: Ref<number[]> = ref([...(props.value || [])]);
51+
const dataGrid = ref();
52+
53+
const onInitialized = (e: DxDataGridTypes.InitializedEvent): void => {
54+
dataGrid.value = e.component;
55+
};
56+
57+
watch(() => props.value, (newValue: number[] | undefined) => {
58+
currentValue.value = [...(newValue || [])];
59+
}, { deep: true });
60+
</script>

0 commit comments

Comments
 (0)