add ability to edit and move form elements
This commit is contained in:
@ -1,10 +1,13 @@
|
||||
<template>
|
||||
<form @submit.prevent="handleSubmit" class="">
|
||||
<h3 v-if="!!formTitle" class="text-xl font-semibold mb-4">{{formTitle}}</h3>
|
||||
<div v-for="input in formInputs" :key="input.id" class="flex flex-col">
|
||||
<div v-for="(input, index) in formInputs" :key="input.id" class="flex flex-col">
|
||||
<!-- if showLabel is undefined or set to true then show the label-->
|
||||
<label v-if="input.showLabel === undefined || input.showLabel" :for="input.id" class="mb-2 font-semibold text-gray-700">{{ input.label }}</label>
|
||||
|
||||
<!-- Description -->
|
||||
<p v-if="input.type === 'description'" class="text-gray-600 mb-2">{{ input.defaultValue }}</p>
|
||||
|
||||
<!-- Text, Email, Password, Number -->
|
||||
<input
|
||||
v-if="['text', 'email', 'password', 'number', 'url', 'tel', 'search', 'color', 'date', 'datetime-local', 'month', 'time', 'week'].includes(input.type)"
|
||||
@ -94,6 +97,13 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row w-auto" v-if="isEditing">
|
||||
<button class="px-2 bg-gray-200 text-black font-semibold rounded-md shadow-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-300 mt-2 mr-2 " @click.prevent="() => {handleEditRequest(input.id)}">edit</button>
|
||||
<button class="px-2 bg-gray-200 text-black font-semibold rounded-md shadow-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-300 mt-2" @click.prevent="() => {handleDeleteRequest(input.id)}">delete</button>
|
||||
<button v-if="index !== 0" class="px-2 bg-gray-200 text-black font-semibold rounded-md shadow-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-300 mt-2 mr-2 ml-auto" @click.prevent="() => {handleMoveUp(input.id)}">move up</button>
|
||||
<button v-if="index !== formInputs.length -1" :class="index === 0 ? 'ml-auto': ''" class="px-2 bg-gray-200 text-black font-semibold rounded-md shadow-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-300 mt-2" @click.prevent="() => {handleMoveDown(input.id)}">move down</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<button type="submit" class="px-6 py-3 bg-blue-600 text-white font-semibold rounded-md shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
{{ !!submitLabel ? submitLabel : 'Submit' }}
|
||||
@ -102,7 +112,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import {reactive, ref, watchEffect} from 'vue';
|
||||
|
||||
export interface FormInput {
|
||||
id: string;
|
||||
@ -117,21 +127,23 @@ export interface FormInput {
|
||||
options?: { label: string, value: string }[]; // For select, radio, and checkbox types
|
||||
}
|
||||
|
||||
const props = defineProps<{ formInputs: FormInput[], submitLabel?: string, formTitle?: string }>();
|
||||
const emit = defineEmits(['send'])
|
||||
const props = defineProps<{ formInputs: FormInput[], submitLabel?: string, formTitle?: string, isEditing?: boolean }>();
|
||||
const emit = defineEmits(['send', 'edit', 'delete', 'move-up', 'move-down']);
|
||||
|
||||
const formData = reactive<Record<string, any>>({});
|
||||
|
||||
// Initialize formData with default values
|
||||
props.formInputs.forEach(input => {
|
||||
if (input.type === 'checkbox' || input.type === 'multi-input') {
|
||||
// if input.defaultValue is an array, use it,
|
||||
// otherwise create an empty array and
|
||||
// if input.defaultValue is a value that matches an option value, insert it into the array
|
||||
formData[input.id] = Array.isArray(input.defaultValue) ? input.defaultValue : (input.defaultValue ? [input.defaultValue] : []);
|
||||
} else {
|
||||
formData[input.id] = input.defaultValue || '';
|
||||
}
|
||||
watchEffect(() => {
|
||||
// Initialize formData with default values
|
||||
props.formInputs.forEach(input => {
|
||||
if (input.type === 'checkbox' || input.type === 'multi-input') {
|
||||
// if input.defaultValue is an array, use it,
|
||||
// otherwise create an empty array and
|
||||
// if input.defaultValue is a value that matches an option value, insert it into the array
|
||||
formData[input.id] = Array.isArray(input.defaultValue) ? input.defaultValue : (input.defaultValue ? [input.defaultValue] : []);
|
||||
} else {
|
||||
formData[input.id] = input.defaultValue || '';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const multiInputValue = ref<string>('');
|
||||
@ -141,6 +153,22 @@ const handleSubmit = () => {
|
||||
emit('send', formData);
|
||||
};
|
||||
|
||||
const handleEditRequest = (inputID: string) => {
|
||||
emit('edit', props.formInputs.find(input => input.id === inputID));
|
||||
};
|
||||
|
||||
const handleDeleteRequest = (inputID: string) => {
|
||||
emit('delete', inputID);
|
||||
};
|
||||
|
||||
const handleMoveUp = (inputID: string) => {
|
||||
emit('move-up', inputID);
|
||||
};
|
||||
|
||||
const handleMoveDown = (inputID: string) => {
|
||||
emit('move-down', inputID);
|
||||
};
|
||||
|
||||
const addMultiInputValue = (id: string) => {
|
||||
if (multiInputValue.value.trim()) {
|
||||
formData[id].push(multiInputValue.value.trim());
|
||||
|
@ -28,7 +28,15 @@
|
||||
:formInputs="generatedFormInputs"
|
||||
:submitLabel="submitLabel"
|
||||
:formTitle="formTitle"
|
||||
@edit="handleEditRequest"
|
||||
@delete="handleDeleteRequest"
|
||||
@move-up="handleMoveUp"
|
||||
@move-down="handleMoveDown"
|
||||
:is-editing="showEditOption"
|
||||
/>
|
||||
<button
|
||||
@click.prevent="showEditOption = !showEditOption"
|
||||
class="px-2 py-1 bg-gray-200 text-black font-semibold rounded-md shadow-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-300 mt-4">preview: {{showEditOption ? 'off' : 'on'}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -62,6 +70,7 @@ const formBuilderPropertiesInputs = ref<FormInput[]>([
|
||||
const formTitle = ref('');
|
||||
const formDescription = ref('');
|
||||
const submitLabel = ref('');
|
||||
const showEditOption = ref(true);
|
||||
|
||||
const formBuilderFieldInputs = ref<FormInput[]>([
|
||||
{
|
||||
@ -87,7 +96,8 @@ const formBuilderFieldInputs = ref<FormInput[]>([
|
||||
{ label: 'Datetime-local', value: 'datetime-local' },
|
||||
{ label: 'Month', value: 'month' },
|
||||
{ label: 'Time', value: 'time' },
|
||||
{ label: 'Week', value: 'week' }
|
||||
{ label: 'Week', value: 'week' },
|
||||
{ label: 'Description', value: 'description' }
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -151,6 +161,14 @@ const formBuilderFieldInputs = ref<FormInput[]>([
|
||||
}
|
||||
]);
|
||||
|
||||
const defaultFormInput: FormInput = {
|
||||
id: '',
|
||||
label: '',
|
||||
type: '',
|
||||
required: false,
|
||||
showLabel: true
|
||||
};
|
||||
|
||||
const generatedFormInputs = ref<FormInput[]>([]);
|
||||
|
||||
const handleFormPropertiesSubmit = (formData: Record<string, any>) => {
|
||||
@ -159,6 +177,78 @@ const handleFormPropertiesSubmit = (formData: Record<string, any>) => {
|
||||
submitLabel.value = formData.submitLabel;
|
||||
};
|
||||
|
||||
const handleEditRequest = (inputToEdit: FormInput) => {
|
||||
// set formBuilderFieldInputs to the input values
|
||||
console.log(inputToEdit);
|
||||
formBuilderFieldInputs.value.forEach((input: FormInput, i) => {
|
||||
// fieldtype
|
||||
if (input.id === 'fieldType') {
|
||||
input.defaultValue = inputToEdit.type;
|
||||
}
|
||||
// fieldlabel
|
||||
if (input.id === 'fieldLabel') {
|
||||
input.defaultValue = inputToEdit.label;
|
||||
}
|
||||
// fieldid
|
||||
if (input.id === 'fieldID') {
|
||||
input.defaultValue = inputToEdit.id;
|
||||
}
|
||||
// placeholder
|
||||
if (input.id === 'placeholder') {
|
||||
input.defaultValue = inputToEdit.placeholder;
|
||||
}
|
||||
// required
|
||||
if (input.id === 'required') {
|
||||
input.defaultValue = inputToEdit.required ? ['true'] : [];
|
||||
}
|
||||
// showlabel
|
||||
if (input.id === 'showLabel') {
|
||||
input.defaultValue = inputToEdit.showLabel ? ['true'] : [];
|
||||
}
|
||||
// defaultvalue
|
||||
if (input.id === 'defaultValue') {
|
||||
input.defaultValue = inputToEdit.defaultValue;
|
||||
}
|
||||
// min
|
||||
if (input.id === 'min') {
|
||||
input.defaultValue = inputToEdit.min;
|
||||
}
|
||||
// max
|
||||
if (input.id === 'max') {
|
||||
input.defaultValue = inputToEdit.max;
|
||||
}
|
||||
// options
|
||||
if (input.id === 'options') {
|
||||
input.defaultValue = inputToEdit.options ? inputToEdit.options.map(opt => opt.value) : [];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteRequest = (inputID: string) => {
|
||||
const index = generatedFormInputs.value.findIndex(input => input.id === inputID);
|
||||
if (index > -1) {
|
||||
generatedFormInputs.value.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMoveUp = (inputID: string) => {
|
||||
const index = generatedFormInputs.value.findIndex(input => input.id === inputID);
|
||||
if (index > 0) {
|
||||
const temp = generatedFormInputs.value[index];
|
||||
generatedFormInputs.value[index] = generatedFormInputs.value[index - 1];
|
||||
generatedFormInputs.value[index - 1] = temp;
|
||||
}
|
||||
};
|
||||
|
||||
const handleMoveDown = (inputID: string) => {
|
||||
const index = generatedFormInputs.value.findIndex(input => input.id === inputID);
|
||||
if (index < generatedFormInputs.value.length - 1) {
|
||||
const temp = generatedFormInputs.value[index];
|
||||
generatedFormInputs.value[index] = generatedFormInputs.value[index + 1];
|
||||
generatedFormInputs.value[index + 1] = temp;
|
||||
}
|
||||
};
|
||||
|
||||
const handleFormBuilderSubmit = (formData: Record<string, any>) => {
|
||||
const newField: FormInput = {
|
||||
id: formData.fieldID,
|
||||
@ -173,20 +263,62 @@ const handleFormBuilderSubmit = (formData: Record<string, any>) => {
|
||||
options: formData.options ? formData.options.map((opt: string) => ({ label: opt, value: opt })) : undefined
|
||||
};
|
||||
|
||||
generatedFormInputs.value.push(newField);
|
||||
// if field id already exists, replace the previous field otherwise add the new field
|
||||
const existingFieldIndex = generatedFormInputs.value.findIndex(field => field.id === newField.id);
|
||||
if (existingFieldIndex > -1) {
|
||||
generatedFormInputs.value.splice(existingFieldIndex, 1, newField);
|
||||
} else {
|
||||
generatedFormInputs.value.push(newField);
|
||||
}
|
||||
|
||||
// Reset form builder fields
|
||||
formBuilderFieldInputs.value.forEach(input => {
|
||||
if (input.type === 'checkbox' || input.type === 'multi-input') {
|
||||
formData[input.id] = [];
|
||||
if (input.id === 'showLabel') { // showLabel should default to true
|
||||
formData[input.id] = ['true'];
|
||||
}
|
||||
} else {
|
||||
formData[input.id] = '';
|
||||
resetBuilderFields();
|
||||
};
|
||||
|
||||
function resetBuilderFields() {
|
||||
formBuilderFieldInputs.value.forEach((input: FormInput, i) => {
|
||||
// fieldtype
|
||||
if (input.id === 'fieldType') {
|
||||
input.defaultValue = defaultFormInput.type;
|
||||
}
|
||||
// fieldlabel
|
||||
if (input.id === 'fieldLabel') {
|
||||
input.defaultValue = defaultFormInput.label;
|
||||
}
|
||||
// fieldid
|
||||
if (input.id === 'fieldID') {
|
||||
input.defaultValue = defaultFormInput.id;
|
||||
}
|
||||
// placeholder
|
||||
if (input.id === 'placeholder') {
|
||||
input.defaultValue = defaultFormInput.placeholder;
|
||||
}
|
||||
// required
|
||||
if (input.id === 'required') {
|
||||
input.defaultValue = defaultFormInput.required ? ['true'] : [];
|
||||
}
|
||||
// showlabel
|
||||
if (input.id === 'showLabel') {
|
||||
input.defaultValue = defaultFormInput.showLabel ? ['true'] : [];
|
||||
}
|
||||
// defaultvalue
|
||||
if (input.id === 'defaultValue') {
|
||||
input.defaultValue = defaultFormInput.defaultValue;
|
||||
}
|
||||
// min
|
||||
if (input.id === 'min') {
|
||||
input.defaultValue = defaultFormInput.min;
|
||||
}
|
||||
// max
|
||||
if (input.id === 'max') {
|
||||
input.defaultValue = defaultFormInput.max;
|
||||
}
|
||||
// options
|
||||
if (input.id === 'options') {
|
||||
input.defaultValue = defaultFormInput.options ? defaultFormInput.options.map(opt => opt.value) : [];
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -562,6 +562,11 @@ video {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mx-auto {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.mb-2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
@ -578,6 +583,26 @@ video {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.mt-2 {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.ml-auto {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.mr-1 {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.mr-2 {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
@ -594,6 +619,15 @@ video {
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.w-max {
|
||||
width: -moz-max-content;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.w-auto {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.max-w-md {
|
||||
max-width: 28rem;
|
||||
}
|
||||
@ -614,6 +648,10 @@ video {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.items-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
@ -622,6 +660,14 @@ video {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.justify-items-end {
|
||||
justify-items: end;
|
||||
}
|
||||
|
||||
.space-x-2 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
|
||||
@ -661,11 +707,21 @@ video {
|
||||
background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
|
||||
.bg-gray-400 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(156 163 175 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
|
||||
.bg-gray-200 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
|
||||
.p-3 {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
@ -678,6 +734,11 @@ video {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.px-2 {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.px-3 {
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 0.75rem;
|
||||
@ -698,6 +759,11 @@ video {
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.py-1 {
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.text-xl {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem;
|
||||
@ -711,11 +777,21 @@ video {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.text-black {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(0 0 0 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
.text-blue-600 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(37 99 235 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
.text-gray-600 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(75 85 99 / var(--tw-text-opacity, 1));
|
||||
}
|
||||
|
||||
.text-gray-700 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(55 65 81 / var(--tw-text-opacity, 1));
|
||||
@ -743,11 +819,26 @@ video {
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.ring-blue-500 {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1));
|
||||
}
|
||||
|
||||
.ring-gray-100 {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(243 244 246 / var(--tw-ring-opacity, 1));
|
||||
}
|
||||
|
||||
.hover\:bg-blue-700:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(29 78 216 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
|
||||
.hover\:bg-gray-300:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(209 213 219 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
|
||||
.hover\:underline:hover {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
@ -767,3 +858,13 @@ video {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1));
|
||||
}
|
||||
|
||||
.focus\:ring-gray-100:focus {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(243 244 246 / var(--tw-ring-opacity, 1));
|
||||
}
|
||||
|
||||
.focus\:ring-gray-300:focus {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity, 1));
|
||||
}
|
Reference in New Issue
Block a user