Skip to content

Commit 4c4781f

Browse files
Fix: Vue implementation
1 parent 754e96c commit 4c4781f

File tree

6 files changed

+257
-260
lines changed

6 files changed

+257
-260
lines changed

Vue/package-lock.json

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

Vue/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
},
1414
"dependencies": {
1515
"devextreme": "25.1.3",
16+
"devextreme-aspnet-data-nojquery": "^5.1.0",
1617
"devextreme-vue": "25.1.3",
1718
"vue": "^3.2.45",
1819
"vue-router": "^4.1.6"
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
<template>
2+
<div class="dx-viewport demo-container">
3+
<div class="dx-fieldset">
4+
<div class="dx-field">
5+
<div class="dx-field-label">DropDownBox with search and embedded DataGrid</div>
6+
<div class="dx-field-value">
7+
<DxDropDownBox
8+
v-model:value="gridBoxValue"
9+
v-model:opened="gridBoxOpened"
10+
value-expr="OrderNumber"
11+
placeholder="Select a value..."
12+
value-change-event=""
13+
:data-source="dropDownBoxDataSource"
14+
:open-on-field-click="false"
15+
:display-expr="gridBoxDisplayExpr"
16+
:show-clear-button="true"
17+
:accept-custom-value="true"
18+
@value-changed="onValueChanged"
19+
@key-down="onKeyDown"
20+
@input="onInput"
21+
@opened="onOpened"
22+
@closed="onClosed"
23+
>
24+
<template #default>
25+
<DxDataGrid
26+
ref="dataGridRef"
27+
height="100%"
28+
width="100%"
29+
:column-width="100"
30+
v-model:selected-row-keys="gridBoxValue"
31+
v-model:focused-row-index="focusedRowIndex"
32+
v-model:focused-row-key="focusedRowKey"
33+
:data-source="dataSource"
34+
:remote-operations="true"
35+
:focused-row-enabled="true"
36+
:hover-state-enabled="true"
37+
:auto-navigate-to-focused-row="false"
38+
@key-down="dataGridKeyDown"
39+
@content-ready="dataGridContentReady"
40+
>
41+
<DxColumn
42+
data-field="OrderNumber"
43+
caption="ID"
44+
data-type="number"
45+
/>
46+
<DxColumn
47+
data-field="OrderDate"
48+
data-type="date"
49+
format="shortDate"
50+
/>
51+
<DxColumn
52+
data-field="StoreState"
53+
data-type="string"
54+
/>
55+
<DxColumn
56+
data-field="StoreCity"
57+
data-type="string"
58+
/>
59+
<DxColumn
60+
data-field="Employee"
61+
data-type="string"
62+
/>
63+
<DxColumn
64+
data-field="SaleAmount"
65+
data-type="number"
66+
>
67+
<DxFormat
68+
type="currency"
69+
:precision="2"
70+
/>
71+
</DxColumn>
72+
<DxPaging
73+
:enabled="true"
74+
:page-size="10"
75+
/>
76+
<DxSelection mode="single"/>
77+
<DxScrolling mode="virtual"/>
78+
</DxDataGrid>
79+
</template>
80+
</DxDropDownBox>
81+
</div>
82+
</div>
83+
</div>
84+
</div>
85+
</template>
86+
87+
<script setup lang="ts">
88+
import { ref } from 'vue';
89+
import DataSource from 'devextreme/data/data_source';
90+
import DxDropDownBox from 'devextreme-vue/drop-down-box';
91+
import type { DxDataGridTypes } from 'devextreme-vue/data-grid';
92+
import type { DxDropDownBoxTypes } from 'devextreme-vue/drop-down-box';
93+
import { DxDataGrid, DxColumn, DxSelection, DxFormat, DxPaging, DxScrolling } from 'devextreme-vue/data-grid';
94+
import notify from 'devextreme/ui/notify';
95+
import type dxDropDownBox from 'devextreme/ui/drop_down_box';
96+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
97+
// @ts-ignore third-party store no types
98+
import * as AspNetData from 'devextreme-aspnet-data-nojquery';
99+
100+
interface OrderItem {
101+
OrderNumber: number;
102+
Employee: string;
103+
StoreState: string;
104+
StoreCity: string;
105+
OrderDate: string;
106+
SaleAmount: number;
107+
}
108+
109+
const dataGridRef = ref<InstanceType<typeof DxDataGrid> | null>(null);
110+
const gridBoxValue = ref<number[]>([35711]);
111+
const gridBoxOpened = ref<boolean>(false);
112+
const focusedRowIndex = ref<number>(0);
113+
const focusedRowKey = ref<number | null>(null);
114+
const searchTimer = ref<ReturnType<typeof setTimeout> | null>(null);
115+
const dataGridFirstLoadCompleted = ref<boolean>(false);
116+
const dataSource = ref<DataSource>(
117+
new DataSource({
118+
store: makeAsyncDataSource(),
119+
searchExpr: ['StoreCity', 'StoreState', 'Employee']
120+
} as any)
121+
);
122+
const dropDownBoxDataSource = ref<DataSource>(
123+
new DataSource({
124+
store: makeAsyncDataSource()
125+
} as any)
126+
);
127+
128+
function makeAsyncDataSource(): unknown {
129+
return AspNetData.createStore({
130+
key: 'OrderNumber',
131+
loadUrl: 'https://js.devexpress.com/Demos/WidgetsGalleryDataService/api/orders'
132+
});
133+
}
134+
135+
function gridBoxDisplayExpr(item: OrderItem): string {
136+
return item ? `${item.Employee}: ${item.StoreState} - ${item.StoreCity} <${item.OrderNumber}>` : '';
137+
}
138+
139+
function onValueChanged(): void {
140+
if (searchTimer.value) clearTimeout(searchTimer.value);
141+
gridBoxOpened.value = false;
142+
}
143+
144+
function isSearchIncomplete(dropDownBox: any): boolean {
145+
const displayValue = dropDownBox.option('displayValue') as string[] | undefined;
146+
const text = dropDownBox.option('text') as string | undefined;
147+
const textValue = text?.length ? text : undefined;
148+
const displayFirst = displayValue?.length ? displayValue[0] : undefined;
149+
return textValue !== displayFirst;
150+
}
151+
152+
function onInput(e: DxDropDownBoxTypes.InputEvent): void {
153+
if (searchTimer.value) clearTimeout(searchTimer.value);
154+
searchTimer.value = setTimeout(() => {
155+
const text = e.component.option('text') as string | undefined;
156+
dataSource.value.searchValue(text ?? null);
157+
if (gridBoxOpened.value && isSearchIncomplete(e.component)) {
158+
dataSource.value.load().then((items: OrderItem[]) => {
159+
if (items.length > 0 && dataGridRef.value?.instance) {
160+
focusedRowKey.value = items[0].OrderNumber;
161+
focusedRowIndex.value = 0;
162+
}
163+
}).catch((error: unknown) => notify(error, 'error', 1000));
164+
} else {
165+
gridBoxOpened.value = true;
166+
}
167+
}, 500);
168+
}
169+
170+
function onOpened(e: DxDropDownBoxTypes.OpenedEvent): void {
171+
const ddbInstance = e.component as dxDropDownBox & { isKeyDown?: boolean };
172+
if (ddbInstance.isKeyDown) {
173+
if (!dataGridRef.value?.instance) return;
174+
const inst = dataGridRef.value?.instance;
175+
const contentReadyHandler = (
176+
args: DxDataGridTypes.ContentReadyEvent<OrderItem, number>
177+
): void => {
178+
const gridInstance = args.component;
179+
gridInstance.focus();
180+
gridInstance.off('contentReady', contentReadyHandler as any);
181+
};
182+
if (!dataGridFirstLoadCompleted.value) {
183+
inst.on('contentReady', contentReadyHandler as any);
184+
} else {
185+
const optionChangedHandler = (
186+
args: DxDataGridTypes.OptionChangedEvent<OrderItem, number>
187+
): void => {
188+
const gridInstance = args.component;
189+
if (args.name === 'focusedRowKey' || args.name === 'focusedColumnIndex') {
190+
gridInstance.off('optionChanged', optionChangedHandler as any);
191+
gridInstance.focus();
192+
}
193+
};
194+
inst.on('optionChanged', optionChangedHandler as any);
195+
focusedRowIndex.value = 0;
196+
}
197+
ddbInstance.isKeyDown = false;
198+
} else if (dataGridFirstLoadCompleted.value && isSearchIncomplete(ddbInstance)) {
199+
void dataSource.value.load().then((items: OrderItem[]) => {
200+
if (items.length > 0) focusedRowKey.value = items[0].OrderNumber;
201+
ddbInstance.focus();
202+
});
203+
}
204+
}
205+
206+
function onClosed(e: DxDropDownBoxTypes.ClosedEvent): void {
207+
const ddbInstance = e.component;
208+
const searchValue = dataSource.value.searchValue();
209+
if (isSearchIncomplete(ddbInstance)) {
210+
gridBoxValue.value = [];
211+
}
212+
if (searchValue) {
213+
dataSource.value.searchValue(null);
214+
}
215+
}
216+
217+
function onKeyDown(e: DxDropDownBoxTypes.KeyDownEvent): void {
218+
if (e.event?.keyCode !== 40) return;
219+
const ddbInstance = e.component as dxDropDownBox & { isKeyDown?: boolean };
220+
if (!gridBoxOpened.value) {
221+
ddbInstance.isKeyDown = true;
222+
gridBoxOpened.value = true;
223+
} else if (dataGridRef.value?.instance) {
224+
dataGridRef.value.instance.focus();
225+
}
226+
}
227+
228+
function dataGridContentReady(): void {
229+
if (!dataGridFirstLoadCompleted.value) {
230+
dataGridFirstLoadCompleted.value = true;
231+
}
232+
}
233+
234+
function dataGridKeyDown(e: DxDataGridTypes.KeyDownEvent): void {
235+
if (e.event?.keyCode === 13 && focusedRowKey.value != null) {
236+
gridBoxValue.value = [focusedRowKey.value];
237+
gridBoxOpened.value = false;
238+
}
239+
}
240+
</script>
241+
242+
<style scoped>
243+
.dx-fieldset { margin: 16px; }
244+
.dx-field-label { font-weight: 600; margin-bottom: 8px; }
245+
</style>

Vue/src/components/HomeContent.vue

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,7 @@
11
<script setup lang="ts">
2-
import { computed, ref } from 'vue';
3-
42
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-
}
3+
import DropDownBoxWithDataGrid from './DropDownBoxWithDataGrid.vue';
204
</script>
215
<template>
22-
<div>
23-
<DxButton
24-
:text="buttonText"
25-
@click="clickHandler"
26-
/>
27-
</div>
6+
<DropDownBoxWithDataGrid/>
287
</template>

0 commit comments

Comments
 (0)