<template>
<form class="row g-3 needs-validation custom-input" novalidate @submit.prevent="submitForm()">
<div class="col-md-4 position-relative">
<InputWrapper :title="'First Name'" :required="true">
<InputField
:formSubmitted="formSubmitted"
:errorMessage="'First name is required.'"
v-model:modelValue="tooltipValidationForm.first_name"
:inputId="'tooltip-form-first-name'"
:placeholder="'Enter first name'"
:tooltipValidation="true"
/>
</InputWrapper>
</div>
<div class="col-md-4 position-relative">
<InputWrapper :title="'Last Name'" :required="true">
<InputField
:formSubmitted="formSubmitted"
:errorMessage="'Last name is required.'"
v-model:modelValue="tooltipValidationForm.last_name"
:inputId="'tooltip-form-last-name'"
:placeholder="'Enter last name'"
:tooltipValidation="true"
/>
</InputWrapper>
</div>
<div class="col-md-4 position-relative">
<InputWrapper :title="'Username'" :required="true">
<div class="input-group has-validation">
<span class="input-group-text" id="validationTooltipUsernamePrepend">@</span>
<InputField
:formSubmitted="formSubmitted"
:errorMessage="'User name is required.'"
v-model:modelValue="tooltipValidationForm.user_name"
:inputId="'tooltip-form-user-name'"
:placeholder="'Enter user name'"
:tooltipValidation="true"
/>
</div>
</InputWrapper>
</div>
<div class="col-md-6 position-relative">
<InputWrapper :title="'City'" :required="true">
<InputField
:formSubmitted="formSubmitted"
:errorMessage="'City is required.'"
v-model:modelValue="tooltipValidationForm.city"
:inputId="'tooltip-form-city'"
:placeholder="'Enter city'"
:tooltipValidation="true"
/>
</InputWrapper>
</div>
<div class="col-md-3 position-relative">
<InputWrapper :title="'State'" :required="true">
<Select
getValueKey="label"
display-key="label"
:placeholder="'Select state'"
v-model="tooltipValidationForm.state"
:errorMessage="'Please select state.'"
:options="states"
:formSubmitted="formSubmitted"
:tooltipValidation="true"
/>
</InputWrapper>
</div>
<div class="col-md-3 position-relative">
<InputWrapper :title="'Zip'" :required="true">
<InputField
:formSubmitted="formSubmitted"
:errorMessage="'Zip is required.'"
v-model:modelValue="tooltipValidationForm.zip"
:inputId="'tooltip-form-zip'"
:placeholder="'Enter zip'"
:tooltipValidation="true"
/>
</InputWrapper>
</div>
<div class="col-12">
<button class="btn btn-primary" type="submit">Submit form</button>
</div>
</form>
</template>
<script setup lang="ts">
import { initInputField, initSelectField } from '~/public/data/common';
import { states } from '~/public/data/country';
import { validateForm } from '~/utils/validators/formValidators';
const { resetForm } = baseUtils();
let formSubmitted = ref(false);
let tooltipValidationForm = reactive({
first_name: initInputField(),
last_name: initInputField(),
user_name: initInputField(),
city: initInputField(),
state: initSelectField(),
zip: initInputField(),
});
function submitForm() {
formSubmitted.value = true;
const { isValid, formData } = validateForm(tooltipValidationForm);
if (isValid) {
tooltipValidationForm = resetForm(tooltipValidationForm);
formSubmitted.value = false;
}
}
</script>
<style scoped></style>
<template>
<label class="form-label" :class="props.class" v-if="!props.labelPositionBottom"
>{{ props.title }}
<span class="txt-danger" v-if="props.required">*</span>
</label>
<slot />
</template>
<script setup lang="ts">
const props = withDefaults(
defineProps<{
title: string;
class?: string;
labelPositionBottom?: boolean;
required?: boolean;
}>(),
{
class: '',
required: false,
},
);
</script>
<style scoped></style>
<template>
<input
:value="modelValue?.data ? modelValue?.data : ''"
:type="inputType"
:id="inputId"
v-bind:placeholder="isPlaceholder ? placeholder || 'Enter Value' : undefined"
class="form-control"
:class="[props.class, { 'is-invalid ': modelValue?.errorMessage && !props.browserValidation }, { 'animated input-shake': animationClass }]"
@input="onInput"
@focusout="onInput"
@focus="showBadge()"
@blur="hideBadge()"
v-if="inputType !== 'textarea' && inputType !== 'file'"
:required="props.required && props.browserValidation"
:disabled="props.disabled"
:list="props.datalist?.length ? `datalistOptions-${inputId}` : undefined"
:maxlength="props.maxlength"
:min="props.inputType === 'date' && props.minDate ? formatDateForInput(props.minDate) : undefined"
:max="props.inputType === 'date' && props.maxDate ? formatDateForInput(props.maxDate) : undefined"
/>
<slot />
<template v-if="modelValue?.errorMessage && required && !props.browserValidation">
<div class="invalid-tooltip" v-if="props.tooltipValidation">{{ modelValue?.errorMessage }}</div>
<div class="invalid-feedback" v-else>{{ modelValue?.errorMessage }}</div>
</template>
<div class="helper-text" v-if="props.helperText">
<p class="fst-italic c-o-light" v-html="`*${props.helperText}`"></p>
</div>
</template>
<script setup lang="ts">
import { validateNonEmptyFields, validateEmail } from '~/utils/validators/InputFieldValidators';
import type { InputField, Select } from '~/types/common';
interface ValidationStatus {
errorMessage: string;
valid: boolean;
}
const props = withDefaults(
defineProps<{
formSubmitted?: boolean;
inputId?: string;
modelValue?: InputField;
errorMessage?: string;
class?: string;
placeholder?: string;
inputType?: string;
required?: boolean;
minLength?: number;
rows?: number;
multiple?: boolean;
helperText?: string;
disabled?: boolean;
tooltipValidation?: boolean;
browserValidation?: boolean;
animation?: boolean;
datalist?: Select[];
maxlength?: number;
isPlaceholder?: boolean;
showLengthBadge?: boolean;
formatValue?: boolean;
formatFunction?: Function | null;
minDate?: Date;
maxDate?: Date;
}>(),
{
inputType: 'text',
required: true,
rows: 3,
multiple: false,
helperText: '',
disabled: false,
tooltipValidation: false,
browserValidation: false,
animation: false,
datalist: () => [],
isPlaceholder: true,
showLengthBadge: false,
formatValue: false,
formatFunction: null,
},
);
const emits = defineEmits(['update:modelValue', 'badgeVisible']);
let validStatus = ref<ValidationStatus>({
errorMessage: '',
valid: false,
});
let changed = ref(false);
const animationClass = ref('');
watch(
() => props.formSubmitted,
() => {
props.formSubmitted && updated(props.modelValue?.data || '');
},
{ deep: true },
);
onMounted(() => {
if (props.formSubmitted) {
updated(props.modelValue?.data || '');
}
});
function onInput(event: Event) {
const target = event.target as HTMLInputElement | null;
if (!target) return;
updated(target.value);
}
function updated(inputValue?: string | number | File | null) {
changed.value = true;
// Handle validation
if (props.required) {
if (props.inputType === 'email') {
validStatus.value = validateEmail(String(inputValue));
} else if (props.inputType === 'file') {
const isValid = !!inputValue;
validStatus.value = {
valid: isValid,
errorMessage: isValid ? '' : props.errorMessage || 'File is required.',
};
} else {
validStatus.value = validateNonEmptyFields({
value: String(inputValue),
minLength: props.minLength,
errorMessage: props.errorMessage,
});
}
} else {
validStatus.value = { valid: true, errorMessage: '' };
}
}
// Prepare emitted value
let data = inputValue;
if (props.inputType === 'number') {
data = inputValue === '' || inputValue == null ? '' : Number(inputValue);
} else if (props.inputType === 'file') {
data = inputValue instanceof File ? inputValue : null;
}
// Emit value
emits('update:modelValue', {
data,
errorMessage: validStatus.value.errorMessage,
});
</script>
<style scoped></style>