Skip to content

Commit 55bcf5f

Browse files
committed
Fix TextInput and DynamicInput values
1 parent 13a6642 commit 55bcf5f

File tree

6 files changed

+138
-31
lines changed

6 files changed

+138
-31
lines changed

src/components/AutoFormFields.vue

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
<template>
2-
<ErrorSummary v-if="!hideSummary" :status="api?.error" :except="visibleFields" />
2+
<ErrorSummary v-if="!hideSummary" :status="api?.error" :except="visibleFields()" />
33
<div :class="flexClass">
44
<div :class="divideClass">
55
<div :class="spaceClass">
66
<fieldset :class="fieldsetClass">
7-
<div v-for="f in supportedFields" :key="f.id" :class="['w-full', f.css?.field ?? (f.type == 'textarea'
7+
<div v-for="f in getSupportedFields()" :key="f.id" :class="['w-full', f.css?.field ?? (f.type == 'textarea'
88
? 'col-span-12'
99
: 'col-span-12 xl:col-span-6' + (f.type == 'checkbox' ? ' flex items-center' : '')),
1010
f.type == 'hidden' ? 'hidden' : '']">
@@ -73,7 +73,7 @@ const type = computed(() => props.metaType ?? typeOf(typeName.value))
7373
const dataModelType = computed(() =>
7474
typeOfRef(metadataApi.value?.operations.find(x => x.request.name == typeName.value)?.dataModel) || type.value)
7575
76-
const supportedFields = computed(() => {
76+
function getSupportedFields() {
7777
const metaType = type.value
7878
if (!metaType) {
7979
if (props.formLayout) {
@@ -92,7 +92,7 @@ const supportedFields = computed(() => {
9292
const metaTypeProps = typeProperties(metaType)
9393
const dataModel = dataModelType.value
9494
const fields = props.formLayout
95-
? props.formLayout
95+
? Array.from(props.formLayout)
9696
: createFormLayout(metaType)
9797
const ret:InputProp[] = []
9898
const op = apiOf(metaType.name)
@@ -107,7 +107,7 @@ const supportedFields = computed(() => {
107107
if (props.configureFormLayout)
108108
props.configureFormLayout(ret)
109109
return ret
110-
})
110+
}
111111
112-
const visibleFields = computed(() => supportedFields.value.filter(x => x.type != 'hidden').map(x => x.id))
112+
const visibleFields = () => getSupportedFields().filter(x => x.type != 'hidden').map(x => x.id)
113113
</script>

src/components/DynamicInput.vue

+6-9
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
<script setup lang="ts">
1414
import type { InputInfo, ApiRequest, ApiResponseType, UploadedFile, InputProp } from '@/types'
15-
import { dateInputFormat, timeInputFormat } from '@/use/utils'
15+
import { textInputValue } from '@/use/utils'
1616
import { Sole } from '@/use/config'
1717
import { lastRightPart, map, omit } from '@servicestack/client'
1818
import { computed, ref, watch } from 'vue'
@@ -32,14 +32,11 @@ const type = computed(() => props.input.type || 'text')
3232
const excludeAttrs = 'ignore,css,options,meta,allowableValues,allowableEntries,op,prop,type,id,name'.split(',')
3333
const inputAttrs = computed(() => omit(props.input, excludeAttrs))
3434
35-
const modelField = ref<any>(map(props.modelValue[props.input.id],
36-
v => props.input.type === 'file'
37-
? null
38-
: (props.input.type === 'date' && v instanceof Date
39-
? dateInputFormat(v)
40-
: props.input.type === 'time'
41-
? timeInputFormat(v)
42-
: v)))
35+
// const m = map(props.modelValue[props.input.id], v => inputValue(props.input.type, v))
36+
// console.log('m', props.input.id, props.input.type, props.modelValue[props.input.id], m)
37+
const modelField = ref<any>(type.value === 'file'
38+
? null
39+
: props.modelValue[props.input.id])
4340
4441
watch(modelField, () => {
4542
props.modelValue[props.input.id] = modelField.value

src/components/TextInput.vue

+14-11
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@
33
<slot name="header" :inputElement="inputElement" :id="id" :modelValue="modelValue" :status="status" v-bind="$attrs"></slot>
44
<label v-if="useLabel" :for="id" :class="`block text-sm font-medium text-gray-700 dark:text-gray-300 ${labelClass??''}`">{{ useLabel }}</label>
55
<div class="mt-1 relative rounded-md shadow-sm">
6-
<input ref="inputElement" :type="useType"
7-
:name="id"
8-
:id="id"
9-
:class="cls"
10-
:placeholder="usePlaceholder"
11-
:value="modelValue"
12-
@input="$emit('update:modelValue', value($event.target))"
13-
:aria-invalid="errorField != null"
14-
:aria-describedby="`${id}-error`"
15-
step="any"
16-
v-bind="omit($attrs, ['class'])">
6+
7+
<input ref="inputElement" :type="useType"
8+
:name="id"
9+
:id="id"
10+
:class="cls"
11+
:placeholder="usePlaceholder"
12+
:value="textInputValue(useType,modelValue)"
13+
@input="$emit('update:modelValue', value($event.target))"
14+
:aria-invalid="errorField != null"
15+
:aria-describedby="`${id}-error`"
16+
step="any"
17+
v-bind="omit($attrs,['class','value'])">
18+
1719
<div v-if="errorField" class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
1820
<svg class="h-5 w-5 text-red-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
1921
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
@@ -37,6 +39,7 @@ import type { ApiState, ResponseStatus } from "../types"
3739
import { errorResponse, humanize, omit, toPascalCase } from "@servicestack/client"
3840
import { computed, inject, ref } from "vue"
3941
import { input } from './css'
42+
import { textInputValue } from '@/use/utils'
4043
4144
const value = (e:EventTarget|null) => (e as HTMLInputElement).value //workaround IDE type-check error
4245

src/demo/App.vue

+75-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,48 @@
99
<SecondaryButton @click="modal = !modal">Modal Dialog</SecondaryButton>
1010
</div>
1111

12+
<div class="mt-8 mx-auto max-w-4xl flex flex-col gap-y-4">
13+
<h3>date</h3>
14+
<div class="grid grid-cols-6 gap-6">
15+
<TextInput class="col-span-2" type="date" id="isoDate7Z" v-model="dates.isoDate7Z" :label="dates.isoDate7Z" required />
16+
<TextInput class="col-span-2" type="date" id="isoDate3Z" v-model="dates.isoDate3Z" :label="dates.isoDate3Z" required />
17+
<TextInput class="col-span-2" type="date" id="isoDateZ" v-model="dates.isoDateZ" :label="dates.isoDateZ" required />
18+
<TextInput class="col-span-2" type="date" id="isoDate" v-model="dates.isoDate" :label="dates.isoDate" required />
19+
<TextInput class="col-span-2" type="date" id="isoDateOnly" v-model="dates.isoDateOnly" :label="dates.isoDateOnly" required />
20+
</div>
21+
22+
<h3>datetime-local</h3>
23+
<div class="grid grid-cols-6 gap-6">
24+
<TextInput class="col-span-2" type="datetime-local" id="isoDate7Z" v-model="dates.isoDate7Z" :label="dates.isoDate7Z" required />
25+
<TextInput class="col-span-2" type="datetime-local" id="isoDate3Z" v-model="dates.isoDate3Z" :label="dates.isoDate3Z" required />
26+
<TextInput class="col-span-2" type="datetime-local" id="isoDateZ" v-model="dates.isoDateZ" :label="dates.isoDateZ" required />
27+
<TextInput class="col-span-2" type="datetime-local" id="isoDate" v-model="dates.isoDate" :label="dates.isoDate" required />
28+
<TextInput class="col-span-2" type="datetime-local" id="isoDateOnly" v-model="dates.isoDateOnly" :label="dates.isoDateOnly" required />
29+
</div>
30+
31+
<h3>Dynamic DateTimes</h3>
32+
<div class="grid grid-cols-6 gap-6">
33+
<div v-for="f in dynamicDateTimes" class="col-span-2">
34+
<DynamicInput :input="f" :modelValue="modelDateTimes" @update:modelValue="modelDateTimes=$event" :api="api" />
35+
<div>{{ modelDateTimes[f.id] }}</div>
36+
</div>
37+
</div>
38+
<div>
39+
<pre>{{ modelDateTimes }}</pre>
40+
</div>
41+
42+
<h3>Dynamic Dates</h3>
43+
<div class="grid grid-cols-6 gap-6">
44+
<div v-for="f in dynamicDates" class="col-span-2">
45+
<DynamicInput :input="f" :modelValue="modelDates" @update:modelValue="modelDates=$event" :api="api" />
46+
<div>{{ modelDates[f.id] }}</div>
47+
</div>
48+
</div>
49+
<div>
50+
<pre>{{ modelDates }}</pre>
51+
</div>
52+
</div>
53+
1254
<div class="mt-8">
1355

1456
<form v-if="show" class="mx-auto max-w-4xl" @submit.prevent="onSubmit">
@@ -111,8 +153,6 @@
111153
</template>
112154
</AutoQueryGrid>
113155

114-
<AutoQueryGrid class="mb-3" type="Customer" />
115-
116156
<AutoQueryGrid class="mb-3" apis="QueryBookings,CreateBooking,UpdateBooking" />
117157

118158
<AutoQueryGrid class="mb-3" type="Booking" selected-columns="id,name,roomType,roomNumber,cost,bookingStartDate" />
@@ -649,7 +689,7 @@
649689
</template>
650690

651691
<script setup lang="ts">
652-
import type { ApiResponse } from '../types'
692+
import type { ApiResponse, InputInfo } from '../types'
653693
import { inject, onMounted, ref } from 'vue'
654694
import { lastRightPart, JsonServiceClient } from '@servicestack/client'
655695
import { useConfig, useMetadata, useFiles, useUtils, useFormatters, useAuth } from '../'
@@ -713,6 +753,38 @@ setAutoQueryGridDefaults({
713753
// showCopyApiUrl: false,
714754
})
715755
756+
const dates = {
757+
isoDate7Z: "2024-03-16T12:11:03.8071595Z",
758+
isoDate3Z: "2024-03-16T12:11:03.807Z",
759+
isoDateZ: "2024-03-16T12:11:03Z",
760+
isoDate: "2024-03-16T12:11:03",
761+
isoDateOnly: "2024-03-16",
762+
}
763+
764+
const api:{error?:any} = {}
765+
766+
let modelDateTimes = ref<{[k:string]:string}>({})
767+
const dynamicDateTimes:{[k:string]:InputInfo} = Object.keys(dates).reduce((acc,x) => {
768+
acc[x] = {
769+
id:x,
770+
type:'datetime-local',
771+
label: dates[x],
772+
value: dates[x]
773+
}
774+
modelDateTimes.value[x] = dates[x]
775+
return acc }, {})
776+
777+
let modelDates = ref<{[k:string]:string}>({})
778+
const dynamicDates:{[k:string]:InputInfo} = Object.keys(dates).reduce((acc,x) => {
779+
acc[x] = {
780+
id:x,
781+
type:'date',
782+
label: dates[x],
783+
value: dates[x]
784+
}
785+
modelDates.value[x] = dates[x]
786+
return acc }, {})
787+
716788
const client = inject<JsonServiceClient>('client')!
717789
718790
authenticate()

src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export interface UiConfig {
108108
apisResolver?:(type:string|null, metaTypes?:MetadataTypes|null) => AutoQueryApis|null
109109
apiResolver?:(name:string) => MetadataOperationType|null
110110
typeResolver?:(name:string,namespace?:string|null) => MetadataType|null
111+
inputValue?:(type:string,value:any) => string|null
111112
autoQueryGridDefaults?: AutoQueryGridDefaults
112113
storage?:Storage
113114
tableIcon?:ImageInfo

src/use/utils.ts

+36-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,48 @@
11
import type { Ref } from "vue"
22
import { isRef, nextTick, unref } from "vue"
33
import type { ApiRequest, IReturn, TransitionRules } from "@/types"
4-
import { ApiResult, appendQueryString, dateFmt, enc, JsonServiceClient, lastLeftPart, nameOf, omit, setQueryString, toTime } from "@servicestack/client"
4+
import { ApiResult, appendQueryString, enc, JsonServiceClient, lastLeftPart, nameOf, omit, setQueryString, toDate, toTime } from "@servicestack/client"
55
import { assetsPathResolver } from "./config"
66
import { Sole } from "./config"
77

88
/** Format Date into required input[type=date] format */
9-
export function dateInputFormat(d:Date) { return dateFmt(d).replace(/\//g,'-') }
9+
export function dateInputFormat(value:Date|string|Object) {
10+
if (value == null || typeof value == 'object') return ''
11+
const d = toDate(value)
12+
if (d == null || d.toString() == 'Invalid Date') return ''
13+
return d.toISOString().substring(0,10) ?? ''
14+
}
15+
16+
export function dateTimeInputFormat(value:Date|string|Object) {
17+
if (value == null || typeof value == 'object') return ''
18+
const d = toDate(value)
19+
if (d == null || d.toString() == 'Invalid Date') return ''
20+
return d.toISOString().substring(0,19) ?? ''
21+
}
1022

1123
/** Format TimeSpan or Date into required input[type=time] format */
1224
export function timeInputFormat(s?:string|number|Date|null) { return s == null ? '' : toTime(s) }
1325

26+
export function textInputValue(type:string, value:any) {
27+
if (Sole.config.inputValue)
28+
return Sole.config.inputValue(type,value)
29+
let ret = type === 'date'
30+
? dateInputFormat(value)
31+
: type === 'datetime-local'
32+
? dateTimeInputFormat(value)
33+
: type === 'time'
34+
? timeInputFormat(value)
35+
: value
36+
const t = typeof ret
37+
ret = ret == null
38+
? ''
39+
: t == 'boolean' || t == 'number'
40+
? `${ret}`
41+
: ret
42+
return ret
43+
}
44+
45+
1446
/** Double set reactive Ref<T> to force triggering updates */
1547
export function setRef($ref:Ref<any>, value:any) {
1648
$ref.value = null
@@ -216,7 +248,9 @@ export function useUtils() {
216248
return {
217249
LocalStore,
218250
dateInputFormat,
251+
dateTimeInputFormat,
219252
timeInputFormat,
253+
textInputValue,
220254
setRef,
221255
unRefs,
222256
transition,

0 commit comments

Comments
 (0)