Skip to content

Commit 32aa165

Browse files
committed
Add DataGrid + AutoEditForm
1 parent 5f01730 commit 32aa165

15 files changed

+808
-83
lines changed

src/api.ts

+242-40
Large diffs are not rendered by default.

src/components/AutoCreateForm.vue

+82-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
<template>
22

3+
<div v-if="!metaType">
4+
<p class="text-red-700">Could not create form for unknown <b>type</b> {{ typeName }}</p>
5+
</div>
36
<div v-if="formStyle=='card'" :class="panelClass">
47
<form @submit.prevent="save">
58
<div :class="formClass">
@@ -9,14 +12,16 @@
912
<p v-else-if="notes" :class="['notes',subHeadingClass]" v-html="notes"></p>
1013
</div>
1114

12-
<AutoFormFields :modelValue="modelValue" @update:modelValue="update" :api="api" />
13-
15+
<AutoFormFields :modelValue="model" @update:modelValue="update" :api="api" />
16+
1417
</div>
1518
<div :class="Css.form.buttonsClass">
16-
<div></div>
19+
<div>
20+
<FormLoading v-if="showLoading && loading" />
21+
</div>
1722
<div class="flex justify-end">
18-
<SecondaryButton @click="done">Cancel</SecondaryButton>
19-
<PrimaryButton type="submit" class="ml-4">Save</PrimaryButton>
23+
<SecondaryButton @click="close" :disabled="loading">Cancel</SecondaryButton>
24+
<PrimaryButton type="submit" class="ml-4" :disabled="loading">Save</PrimaryButton>
2025
</div>
2126
</div>
2227
</form>
@@ -44,15 +49,17 @@
4449
</div>
4550
</div>
4651

47-
<AutoFormFields :modelValue="modelValue" @update:modelValue="update" :api="api" />
52+
<AutoFormFields :modelValue="model" @update:modelValue="update" :api="api" />
4853

4954
</div>
5055
</div>
5156
<div :class="Css.form.buttonsClass">
52-
<div></div>
57+
<div>
58+
<FormLoading v-if="showLoading && loading" />
59+
</div>
5360
<div class="flex justify-end">
54-
<SecondaryButton @click="close">Cancel</SecondaryButton>
55-
<PrimaryButton type="submit" class="ml-4">Save</PrimaryButton>
61+
<SecondaryButton @click="close" :disabled="loading">Cancel</SecondaryButton>
62+
<PrimaryButton type="submit" class="ml-4" :disabled="loading">Save</PrimaryButton>
5663
</div>
5764
</div>
5865
</form>
@@ -65,14 +72,14 @@
6572
</template>
6673

6774
<script setup lang="ts">
68-
import type { ApiRequest } from '@/types'
69-
import { useAppMetadata, Css } from '@/api'
75+
import type { ApiRequest, ApiResponse, ResponseStatus } from '@/types'
76+
import { useAppMetadata, Css, createDto, formValues, useClient } from '@/api'
7077
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
7178
import { getTypeName, transition } from './utils'
72-
import { ApiResult, humanize } from '@servicestack/client'
79+
import { ApiResult, HttpMethods, humanize, map } from '@servicestack/client'
7380
7481
const props = withDefaults(defineProps<{
75-
modelValue: ApiRequest
82+
type: string|InstanceType<any>|Function
7683
formStyle?: "slideOver" | "card"
7784
panelClass?: string
7885
formClass?: string
@@ -81,32 +88,78 @@ const props = withDefaults(defineProps<{
8188
heading?: string
8289
subHeading?: string
8390
notes?: string
91+
autosave?: boolean
92+
showLoading?: boolean
8493
}>(), {
85-
formStyle: "slideOver",
94+
formStyle: "slideOver",
95+
autosave: true,
96+
showLoading: true
8697
})
8798
8899
const emit = defineEmits<{
89-
(e: "update:modelValue", o:ApiRequest): void
90100
(e:'done'): void
101+
(e:'save', response:any): () => void
102+
(e:'error', status:ResponseStatus): void
91103
}>()
92104
93105
function update(value:ApiRequest) {
94-
emit('update:modelValue', value)
95106
}
96107
108+
const { typeOf, typeProperties } = useAppMetadata()
109+
110+
const typeName = computed(() => typeof props.type == 'string'
111+
? props.type
112+
: (props.type ? getTypeName(new props.type()) : null))
113+
114+
const metaType = computed(() => typeOf(typeName.value))
115+
const model = ref(typeof props.type == 'string' ? createDto(props.type) : props.type ? new props.type() : null)
116+
97117
const panelClass = computed(() => props.panelClass || Css.form.panelClass(props.formStyle))
98118
const formClass = computed(() => props.formClass || Css.form.formClass(props.formStyle))
99119
const headingClass = computed(() => props.headingClass || Css.form.headingClass(props.formStyle))
100120
const subHeadingClass = computed(() => props.subHeadingClass || Css.form.subHeadingClass(props.formStyle))
101121
102-
const { typeOf } = useAppMetadata()
103-
const typeName = computed(() => getTypeName(props.modelValue))
104-
105122
const title = computed(() => props.heading || typeOf(typeName.value)?.description || `New ${humanize(typeName.value)}`)
106123
107-
const api = ref(new ApiResult<any>())
108-
109-
function save() {
124+
const api = ref<ApiResponse>(new ApiResult<any>())
125+
126+
let client = useClient()
127+
let loading = computed(() => client.loading.value)
128+
129+
async function save(e:Event) {
130+
let form = e.target as HTMLFormElement
131+
if (!props.autosave) {
132+
emit('save', new model.value.constructor(formValues(form, typeProperties(metaType.value))))
133+
return
134+
}
135+
136+
let method = map(model.value?.['getMethod'], fn => typeof fn =='function' ? fn() : null) || 'POST'
137+
let returnsVoid = map(model.value?.['createResponse'], fn => typeof fn == 'function' ? fn() : null) == null
138+
139+
if (HttpMethods.hasRequestBody(method)) {
140+
let requestDto = new model.value.constructor()
141+
let formData = new FormData(form)
142+
if (!returnsVoid) {
143+
api.value = await client.apiForm(requestDto, formData, { jsconfig: 'eccn' })
144+
} else {
145+
api.value = await client.apiFormVoid(requestDto, formData, { jsconfig: 'eccn' })
146+
}
147+
} else {
148+
let fieldValues = formValues(form, typeProperties(metaType.value))
149+
let requestDto = new model.value.constructor(fieldValues)
150+
if (!returnsVoid) {
151+
api.value = await client.api(requestDto, { jsconfig: 'eccn' })
152+
} else {
153+
api.value = await client.apiVoid(requestDto, { jsconfig: 'eccn' })
154+
}
155+
}
156+
157+
if (api.value.succeeded) {
158+
form.reset()
159+
emit('save', api.value.response)
160+
} else {
161+
emit('error', api.value.error!)
162+
}
110163
}
111164
112165
function done() {
@@ -125,7 +178,13 @@ watch(show, () => {
125178
if (!show.value) setTimeout(done, 700)
126179
})
127180
show.value = true
128-
const close = () => show.value = false
181+
function close() {
182+
if (props.formStyle == 'slideOver') {
183+
show.value = false
184+
} else {
185+
done()
186+
}
187+
}
129188
130189
const globalKeyHandler = (e:KeyboardEvent) => { if (e.key === 'Escape') close() }
131190
onMounted(() => window.addEventListener('keydown', globalKeyHandler))

0 commit comments

Comments
 (0)