Skip to content

Commit

Permalink
Fix TextInput and DynamicInput values
Browse files Browse the repository at this point in the history
  • Loading branch information
mythz committed Oct 7, 2024
1 parent 13a6642 commit 55bcf5f
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 31 deletions.
12 changes: 6 additions & 6 deletions src/components/AutoFormFields.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<template>
<ErrorSummary v-if="!hideSummary" :status="api?.error" :except="visibleFields" />
<ErrorSummary v-if="!hideSummary" :status="api?.error" :except="visibleFields()" />
<div :class="flexClass">
<div :class="divideClass">
<div :class="spaceClass">
<fieldset :class="fieldsetClass">
<div v-for="f in supportedFields" :key="f.id" :class="['w-full', f.css?.field ?? (f.type == 'textarea'
<div v-for="f in getSupportedFields()" :key="f.id" :class="['w-full', f.css?.field ?? (f.type == 'textarea'
? 'col-span-12'
: 'col-span-12 xl:col-span-6' + (f.type == 'checkbox' ? ' flex items-center' : '')),
f.type == 'hidden' ? 'hidden' : '']">
Expand Down Expand Up @@ -73,7 +73,7 @@ const type = computed(() => props.metaType ?? typeOf(typeName.value))
const dataModelType = computed(() =>
typeOfRef(metadataApi.value?.operations.find(x => x.request.name == typeName.value)?.dataModel) || type.value)
const supportedFields = computed(() => {
function getSupportedFields() {
const metaType = type.value
if (!metaType) {
if (props.formLayout) {
Expand All @@ -92,7 +92,7 @@ const supportedFields = computed(() => {
const metaTypeProps = typeProperties(metaType)
const dataModel = dataModelType.value
const fields = props.formLayout
? props.formLayout
? Array.from(props.formLayout)
: createFormLayout(metaType)
const ret:InputProp[] = []
const op = apiOf(metaType.name)
Expand All @@ -107,7 +107,7 @@ const supportedFields = computed(() => {
if (props.configureFormLayout)
props.configureFormLayout(ret)
return ret
})
}
const visibleFields = computed(() => supportedFields.value.filter(x => x.type != 'hidden').map(x => x.id))
const visibleFields = () => getSupportedFields().filter(x => x.type != 'hidden').map(x => x.id)
</script>
15 changes: 6 additions & 9 deletions src/components/DynamicInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

<script setup lang="ts">
import type { InputInfo, ApiRequest, ApiResponseType, UploadedFile, InputProp } from '@/types'
import { dateInputFormat, timeInputFormat } from '@/use/utils'
import { textInputValue } from '@/use/utils'
import { Sole } from '@/use/config'
import { lastRightPart, map, omit } from '@servicestack/client'
import { computed, ref, watch } from 'vue'
Expand All @@ -32,14 +32,11 @@ const type = computed(() => props.input.type || 'text')
const excludeAttrs = 'ignore,css,options,meta,allowableValues,allowableEntries,op,prop,type,id,name'.split(',')
const inputAttrs = computed(() => omit(props.input, excludeAttrs))
const modelField = ref<any>(map(props.modelValue[props.input.id],
v => props.input.type === 'file'
? null
: (props.input.type === 'date' && v instanceof Date
? dateInputFormat(v)
: props.input.type === 'time'
? timeInputFormat(v)
: v)))
// const m = map(props.modelValue[props.input.id], v => inputValue(props.input.type, v))
// console.log('m', props.input.id, props.input.type, props.modelValue[props.input.id], m)
const modelField = ref<any>(type.value === 'file'
? null
: props.modelValue[props.input.id])
watch(modelField, () => {
props.modelValue[props.input.id] = modelField.value
Expand Down
25 changes: 14 additions & 11 deletions src/components/TextInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@
<slot name="header" :inputElement="inputElement" :id="id" :modelValue="modelValue" :status="status" v-bind="$attrs"></slot>
<label v-if="useLabel" :for="id" :class="`block text-sm font-medium text-gray-700 dark:text-gray-300 ${labelClass??''}`">{{ useLabel }}</label>
<div class="mt-1 relative rounded-md shadow-sm">
<input ref="inputElement" :type="useType"
:name="id"
:id="id"
:class="cls"
:placeholder="usePlaceholder"
:value="modelValue"
@input="$emit('update:modelValue', value($event.target))"
:aria-invalid="errorField != null"
:aria-describedby="`${id}-error`"
step="any"
v-bind="omit($attrs, ['class'])">

<input ref="inputElement" :type="useType"
:name="id"
:id="id"
:class="cls"
:placeholder="usePlaceholder"
:value="textInputValue(useType,modelValue)"
@input="$emit('update:modelValue', value($event.target))"
:aria-invalid="errorField != null"
:aria-describedby="`${id}-error`"
step="any"
v-bind="omit($attrs,['class','value'])">

<div v-if="errorField" class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<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">
<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" />
Expand All @@ -37,6 +39,7 @@ import type { ApiState, ResponseStatus } from "../types"
import { errorResponse, humanize, omit, toPascalCase } from "@servicestack/client"
import { computed, inject, ref } from "vue"
import { input } from './css'
import { textInputValue } from '@/use/utils'
const value = (e:EventTarget|null) => (e as HTMLInputElement).value //workaround IDE type-check error
Expand Down
78 changes: 75 additions & 3 deletions src/demo/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,48 @@
<SecondaryButton @click="modal = !modal">Modal Dialog</SecondaryButton>
</div>

<div class="mt-8 mx-auto max-w-4xl flex flex-col gap-y-4">
<h3>date</h3>
<div class="grid grid-cols-6 gap-6">
<TextInput class="col-span-2" type="date" id="isoDate7Z" v-model="dates.isoDate7Z" :label="dates.isoDate7Z" required />
<TextInput class="col-span-2" type="date" id="isoDate3Z" v-model="dates.isoDate3Z" :label="dates.isoDate3Z" required />
<TextInput class="col-span-2" type="date" id="isoDateZ" v-model="dates.isoDateZ" :label="dates.isoDateZ" required />
<TextInput class="col-span-2" type="date" id="isoDate" v-model="dates.isoDate" :label="dates.isoDate" required />
<TextInput class="col-span-2" type="date" id="isoDateOnly" v-model="dates.isoDateOnly" :label="dates.isoDateOnly" required />
</div>

<h3>datetime-local</h3>
<div class="grid grid-cols-6 gap-6">
<TextInput class="col-span-2" type="datetime-local" id="isoDate7Z" v-model="dates.isoDate7Z" :label="dates.isoDate7Z" required />
<TextInput class="col-span-2" type="datetime-local" id="isoDate3Z" v-model="dates.isoDate3Z" :label="dates.isoDate3Z" required />
<TextInput class="col-span-2" type="datetime-local" id="isoDateZ" v-model="dates.isoDateZ" :label="dates.isoDateZ" required />
<TextInput class="col-span-2" type="datetime-local" id="isoDate" v-model="dates.isoDate" :label="dates.isoDate" required />
<TextInput class="col-span-2" type="datetime-local" id="isoDateOnly" v-model="dates.isoDateOnly" :label="dates.isoDateOnly" required />
</div>

<h3>Dynamic DateTimes</h3>
<div class="grid grid-cols-6 gap-6">
<div v-for="f in dynamicDateTimes" class="col-span-2">
<DynamicInput :input="f" :modelValue="modelDateTimes" @update:modelValue="modelDateTimes=$event" :api="api" />
<div>{{ modelDateTimes[f.id] }}</div>
</div>
</div>
<div>
<pre>{{ modelDateTimes }}</pre>
</div>

<h3>Dynamic Dates</h3>
<div class="grid grid-cols-6 gap-6">
<div v-for="f in dynamicDates" class="col-span-2">
<DynamicInput :input="f" :modelValue="modelDates" @update:modelValue="modelDates=$event" :api="api" />
<div>{{ modelDates[f.id] }}</div>
</div>
</div>
<div>
<pre>{{ modelDates }}</pre>
</div>
</div>

<div class="mt-8">

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

<AutoQueryGrid class="mb-3" type="Customer" />

<AutoQueryGrid class="mb-3" apis="QueryBookings,CreateBooking,UpdateBooking" />

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

<script setup lang="ts">
import type { ApiResponse } from '../types'
import type { ApiResponse, InputInfo } from '../types'
import { inject, onMounted, ref } from 'vue'
import { lastRightPart, JsonServiceClient } from '@servicestack/client'
import { useConfig, useMetadata, useFiles, useUtils, useFormatters, useAuth } from '../'
Expand Down Expand Up @@ -713,6 +753,38 @@ setAutoQueryGridDefaults({
// showCopyApiUrl: false,
})
const dates = {
isoDate7Z: "2024-03-16T12:11:03.8071595Z",
isoDate3Z: "2024-03-16T12:11:03.807Z",
isoDateZ: "2024-03-16T12:11:03Z",
isoDate: "2024-03-16T12:11:03",
isoDateOnly: "2024-03-16",
}
const api:{error?:any} = {}
let modelDateTimes = ref<{[k:string]:string}>({})
const dynamicDateTimes:{[k:string]:InputInfo} = Object.keys(dates).reduce((acc,x) => {
acc[x] = {
id:x,
type:'datetime-local',
label: dates[x],
value: dates[x]
}
modelDateTimes.value[x] = dates[x]
return acc }, {})
let modelDates = ref<{[k:string]:string}>({})
const dynamicDates:{[k:string]:InputInfo} = Object.keys(dates).reduce((acc,x) => {
acc[x] = {
id:x,
type:'date',
label: dates[x],
value: dates[x]
}
modelDates.value[x] = dates[x]
return acc }, {})
const client = inject<JsonServiceClient>('client')!
authenticate()
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export interface UiConfig {
apisResolver?:(type:string|null, metaTypes?:MetadataTypes|null) => AutoQueryApis|null
apiResolver?:(name:string) => MetadataOperationType|null
typeResolver?:(name:string,namespace?:string|null) => MetadataType|null
inputValue?:(type:string,value:any) => string|null
autoQueryGridDefaults?: AutoQueryGridDefaults
storage?:Storage
tableIcon?:ImageInfo
Expand Down
38 changes: 36 additions & 2 deletions src/use/utils.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,48 @@
import type { Ref } from "vue"
import { isRef, nextTick, unref } from "vue"
import type { ApiRequest, IReturn, TransitionRules } from "@/types"
import { ApiResult, appendQueryString, dateFmt, enc, JsonServiceClient, lastLeftPart, nameOf, omit, setQueryString, toTime } from "@servicestack/client"
import { ApiResult, appendQueryString, enc, JsonServiceClient, lastLeftPart, nameOf, omit, setQueryString, toDate, toTime } from "@servicestack/client"
import { assetsPathResolver } from "./config"
import { Sole } from "./config"

/** Format Date into required input[type=date] format */
export function dateInputFormat(d:Date) { return dateFmt(d).replace(/\//g,'-') }
export function dateInputFormat(value:Date|string|Object) {
if (value == null || typeof value == 'object') return ''
const d = toDate(value)
if (d == null || d.toString() == 'Invalid Date') return ''
return d.toISOString().substring(0,10) ?? ''
}

export function dateTimeInputFormat(value:Date|string|Object) {
if (value == null || typeof value == 'object') return ''
const d = toDate(value)
if (d == null || d.toString() == 'Invalid Date') return ''
return d.toISOString().substring(0,19) ?? ''
}

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

export function textInputValue(type:string, value:any) {
if (Sole.config.inputValue)
return Sole.config.inputValue(type,value)
let ret = type === 'date'
? dateInputFormat(value)
: type === 'datetime-local'
? dateTimeInputFormat(value)
: type === 'time'
? timeInputFormat(value)
: value
const t = typeof ret
ret = ret == null
? ''
: t == 'boolean' || t == 'number'
? `${ret}`
: ret
return ret
}


/** Double set reactive Ref<T> to force triggering updates */
export function setRef($ref:Ref<any>, value:any) {
$ref.value = null
Expand Down Expand Up @@ -216,7 +248,9 @@ export function useUtils() {
return {
LocalStore,
dateInputFormat,
dateTimeInputFormat,
timeInputFormat,
textInputValue,
setRef,
unRefs,
transition,
Expand Down

0 comments on commit 55bcf5f

Please sign in to comment.