Skip to content

Commit 031fdfa

Browse files
Vue: Implement FileUploader in DataGrid edit form with modern Composition API
- Use Vue 3 Composition API with proper TypeScript typing - Implement reactive refs with proper type annotations - Create modular higher-order function for upload handler - Add comprehensive error handling for file uploads - Include proper styling with CSS Grid and flexbox - Remove original implementation files for clean structure - Add backend URL constants for configuration - Implement proper template refs with type safety
1 parent 338673d commit 031fdfa

File tree

5 files changed

+204
-222
lines changed

5 files changed

+204
-222
lines changed

Vue/src/assets/orig_main.css

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

Vue/src/components/HomeContent.vue

Lines changed: 202 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,209 @@
11
<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;
2+
import { ref, type Ref } from "vue";
3+
4+
import "devextreme/dist/css/dx.material.blue.light.compact.css";
5+
6+
import DxDataGrid, {
7+
DxColumn,
8+
DxEditing,
9+
DxPopup,
10+
DxForm,
11+
} from "devextreme-vue/data-grid";
12+
import DxFileUploader from "devextreme-vue/file-uploader";
13+
import { DxItem } from "devextreme-vue/form";
14+
import DxButton from "devextreme-vue/button";
15+
16+
import type {
17+
UploadedEvent,
18+
UploadErrorEvent,
19+
ValueChangedEvent,
20+
} from "devextreme/ui/file_uploader";
21+
import type {
22+
ColumnEditCellTemplateData,
23+
EditCanceledEvent,
24+
SavedEvent,
25+
} from "devextreme/ui/data_grid";
26+
import type { ClickEvent } from "devextreme/ui/button";
27+
import { employees, type Employee } from "../data";
28+
import { backendURL } from "../constants";
29+
30+
// Reactive refs
31+
const fileUploaderRef: Ref<InstanceType<typeof DxFileUploader> | null> = ref(null);
32+
const imageRef: Ref<HTMLImageElement | null> = ref(null);
33+
const retryButtonVisible = ref<boolean>(false);
34+
35+
// Event handlers with proper typing
36+
function onClick(_e: ClickEvent): void {
37+
// The retry UI/API is not implemented. Use the private API as shown at T611719.
38+
const fileUploaderInstance = fileUploaderRef.value?.instance;
39+
if (fileUploaderInstance) {
40+
// @ts-expect-error: Accessing private API for retry functionality
41+
for (let i = 0; i < fileUploaderInstance._files.length; i++) {
42+
// @ts-expect-error: Accessing private API for retry functionality
43+
delete fileUploaderInstance._files[i].uploadStarted;
44+
}
45+
fileUploaderInstance.upload();
46+
}
47+
}
48+
49+
function onValueChanged(e: ValueChangedEvent): void {
50+
if (e.value && e.value.length > 0) {
51+
const reader = new FileReader();
52+
reader.onload = (args) => {
53+
if (typeof args.target?.result === "string" && imageRef.value) {
54+
imageRef.value.src = args.target.result;
55+
}
56+
};
57+
reader.readAsDataURL(e.value[0]); // convert to base64 string
58+
}
59+
}
60+
61+
// Higher-order function for upload success handler
62+
const createUploadedHandler = (cellInfo: ColumnEditCellTemplateData<Employee, number>) =>
63+
(e: UploadedEvent): void => {
64+
if (e.request?.responseText) {
65+
cellInfo.setValue("images/employees/" + e.request.responseText);
66+
retryButtonVisible.value = false;
67+
}
68+
};
69+
70+
function onUploadError(e: UploadErrorEvent): void {
71+
const xhttp = e.request;
72+
if (xhttp && xhttp.status === 400) {
73+
e.message = e.error?.responseText || "Upload error";
74+
}
75+
if (xhttp && xhttp.readyState === 4 && xhttp.status === 0) {
76+
e.message = "Connection refused";
77+
}
78+
retryButtonVisible.value = true;
79+
}
80+
81+
function onEditCanceled(_e: EditCanceledEvent<Employee, number>): void {
82+
if (retryButtonVisible.value) {
83+
retryButtonVisible.value = false;
84+
}
85+
}
86+
87+
function onSaved(_e: SavedEvent<Employee, number>): void {
88+
if (retryButtonVisible.value) {
89+
retryButtonVisible.value = false;
90+
}
1991
}
2092
</script>
93+
2194
<template>
2295
<div>
23-
<DxButton
24-
:text="buttonText"
25-
@click="clickHandler"
26-
/>
96+
<DxDataGrid
97+
id="gridContainer"
98+
:data-source="employees"
99+
key-expr="ID"
100+
:show-borders="true"
101+
@saved="onSaved"
102+
@edit-canceled="onEditCanceled"
103+
>
104+
<DxEditing :allow-updating="true" mode="popup">
105+
<DxPopup :show-title="true" :width="700" title="Employee Info" />
106+
<DxForm>
107+
<DxItem :col-count="2" :col-span="2" item-type="group">
108+
<DxItem data-field="Prefix" />
109+
<DxItem data-field="FirstName" />
110+
<DxItem data-field="LastName" />
111+
<DxItem data-field="Position" />
112+
<DxItem data-field="BirthDate" />
113+
<DxItem data-field="HireDate" />
114+
</DxItem>
115+
<DxItem
116+
:col-count="2"
117+
:col-span="2"
118+
item-type="group"
119+
caption="Photo"
120+
>
121+
<DxItem data-field="Picture" :col-span="2" />
122+
</DxItem>
123+
</DxForm>
124+
</DxEditing>
125+
<DxColumn
126+
data-field="Picture"
127+
:width="70"
128+
:allow-sorting="false"
129+
cell-template="cellTemplate"
130+
edit-cell-template="editCellTemplate"
131+
/>
132+
<DxColumn data-field="Prefix" :width="70" caption="Title" />
133+
<DxColumn data-field="FirstName" />
134+
<DxColumn data-field="LastName" />
135+
<DxColumn data-field="Position" />
136+
<DxColumn data-field="BirthDate" data-type="date" />
137+
<DxColumn data-field="HireDate" data-type="date" />
138+
139+
<!-- Cell template for displaying images -->
140+
<template #cellTemplate="{ data }">
141+
<img
142+
:src="backendURL + data.value"
143+
alt="employee pic"
144+
style="max-width: 100%; height: auto;"
145+
/>
146+
</template>
147+
148+
<!-- Edit cell template for file upload -->
149+
<template #editCellTemplate="{ data }">
150+
<div class="file-uploader-container">
151+
<img
152+
ref="imageRef"
153+
class="uploadedImage"
154+
:src="backendURL + data.value"
155+
alt="employee pic"
156+
/>
157+
<DxFileUploader
158+
ref="fileUploaderRef"
159+
:multiple="false"
160+
accept="image/*"
161+
upload-mode="instantly"
162+
:upload-url="backendURL + 'FileUpload/post'"
163+
@value-changed="onValueChanged"
164+
@uploaded="createUploadedHandler(data)"
165+
@upload-error="onUploadError"
166+
/>
167+
<DxButton
168+
class="retryButton"
169+
text="Retry"
170+
:visible="retryButtonVisible"
171+
@click="onClick"
172+
/>
173+
</div>
174+
</template>
175+
</DxDataGrid>
27176
</div>
28177
</template>
178+
179+
<style scoped>
180+
#gridContainer {
181+
min-height: 530px;
182+
width: 100%;
183+
max-width: 1000px;
184+
}
185+
186+
.dx-row img {
187+
height: 50px;
188+
object-fit: cover;
189+
}
190+
191+
.file-uploader-container {
192+
display: flex;
193+
flex-direction: column;
194+
gap: 10px;
195+
}
196+
197+
.uploadedImage {
198+
max-width: 150px;
199+
max-height: 150px;
200+
border: 1px solid #ddd;
201+
border-radius: 4px;
202+
object-fit: cover;
203+
}
204+
205+
.retryButton {
206+
margin-top: 10px;
207+
align-self: flex-start;
208+
}
209+
</style>

0 commit comments

Comments
 (0)