handle advanced update + enhancements

* after mount, advanced mode is working with a copy and updatable template JSON that is reset on switch
*remove cache with KeepAlive because this didn't fix the issue that inputs aren't in DOM for submit and instead listen to inputs in order to update the JSON files, JSON files will be use to submit data
*add aria-hidden on select to avoid conflict with custom one for accessibility
*update some inputs in order to work with the advanced mode listening inputs event
* update datepicker to fit others props name allowing a default date
This commit is contained in:
Jordan Blasenhauer 2024-06-13 18:43:53 +02:00
parent fafd7007f3
commit 05ad7373a7
8 changed files with 95 additions and 32 deletions

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
<script setup>
import { defineProps, reactive, onMounted, computed, KeepAlive } from "vue";
import { defineProps, reactive, onMounted, computed, onUnmounted } from "vue";
import Container from "@components/Widget/Container.vue";
import Fields from "@components/Form/Fields.vue";
import Title from "@components/Widget/Title.vue";
@ -70,6 +70,7 @@ const data = reactive({
keyword: "",
type: "all",
context: "all",
base: JSON.parse(JSON.stringify(props.template)),
filtered: computed(() => {
const filterPlugin = [
{
@ -101,7 +102,7 @@ const data = reactive({
];
// Deep copy
const template = JSON.parse(JSON.stringify(props.template));
const template = JSON.parse(JSON.stringify(data.base));
// Start plugin filtering
const filterPlugins = useFilter(template, filterPlugin);
// Filter settings
@ -226,19 +227,74 @@ const buttonSave = {
disabled: false,
color: "success",
size: "normal",
type: "submit",
type: "bouton",
attrs: {
"data-submit-form": JSON.stringify(data.base),
},
containerClass: "flex justify-center",
};
function updateInp(e) {
// Check if target is child of data-advanced-form
if (!e.target.closest("[data-advanced-form-plugin]")) return;
// Wait some ms that previous update logic is done like datepicker
setTimeout(() => {
let inpId, inpValue;
// Case target is input (a little different for datepicker)
if (e.target.tagName === "INPUT") {
inpId = e.target.id;
inpValue = e.target.hasAttribute("data-timestamp")
? e.target.getAttribute("data-timestamp")
: e.target.value;
}
// Case target is select
if (
e.target.closest("[data-field-container]") &&
e.target.hasAttribute("data-setting-id") &&
e.target.hasAttribute("data-setting-value")
) {
inpId = e.target.getAttribute("data-setting-id");
inpValue = e.target.getAttribute("data-setting-value");
}
// Case target is not an input-like
if (!inpId) return;
data.base.find((plugin) => {
const settings = plugin["settings"];
// loop on each settings from plugin
for (const [key, value] of Object.entries(settings)) {
if (value.id === inpId) {
value.value = inpValue;
}
}
});
}, 50);
}
onMounted(() => {
// Get first props.forms template name
data.currPlugin = getFirstPlugin(props.template);
data.plugins = getPluginNames(props.template);
// Store update data on
window.addEventListener("input", updateInp);
window.addEventListener("change", updateInp);
window.addEventListener("click", updateInp);
});
onUnmounted(() => {
window.removeEventListener("input", updateInp);
window.removeEventListener("change", updateInp);
window.removeEventListener("click", updateInp);
});
</script>
<template>
<Container
data-advanced-form
:tag="'form'"
method="POST"
:containerClass="`col-span-12 w-full m-1 p-1`"
@ -258,24 +314,23 @@ onMounted(() => {
<Select @inp="(v) => (data.context = v)" v-bind="selectContext" />
</Container>
<template v-for="plugin in data.filtered">
<KeepAlive>
<Container
v-if="plugin.name === data.currPlugin"
class="col-span-12 w-full"
>
<Title type="card" :title="plugin.name" />
<Subtitle type="card" :subtitle="plugin.description" />
<Container
data-advanced-form-plugin
v-if="plugin.name === data.currPlugin"
class="col-span-12 w-full"
>
<Title type="card" :title="plugin.name" />
<Subtitle type="card" :subtitle="plugin.description" />
<Container class="grid grid-cols-12 w-full relative">
<template
v-for="(setting, name, index) in plugin.settings"
:key="index"
>
<Fields :setting="setting" />
</template>
</Container>
<Container class="grid grid-cols-12 w-full relative">
<template
v-for="(setting, name, index) in plugin.settings"
:key="index"
>
<Fields :setting="setting" />
</template>
</Container>
</KeepAlive>
</Container>
</template>
<Button v-bind="buttonSave" />
</Container>

View file

@ -98,6 +98,7 @@ onMounted(() => {
<template>
<Container
data-easy-form
:tag="'form'"
method="POST"
:containerClass="`col-span-12 w-full m-1 p-1`"

View file

@ -137,6 +137,7 @@ const buttonSave = {
<template>
<Container
data-raw-form
:tag="'form'"
method="POST"
:containerClass="`col-span-12 w-full m-1 p-1`"

View file

@ -286,7 +286,7 @@ const emits = defineEmits(["inp"]);
:headerClass="props.headerClass"
/>
<select :name="props.name" class="hidden">
<select aria-hidden="true" :name="props.name" class="hidden">
<option
v-for="(value, id) in props.values"
:key="id"
@ -414,6 +414,8 @@ const emits = defineEmits(["inp"]);
: '',
'select-dropdown-btn',
]"
:data-setting-id="props.id"
:data-setting-value="value"
:aria-controls="`${props.id}-text`"
:aria-checked="
(select.value && select.value === value) ||

View file

@ -24,7 +24,7 @@ import "@assets/css/flatpickr.dark.css";
columns : {"pc": 6, "tablet": 12, "mobile": 12},
disabled: false,
required: true,
defaultDate: 1735682600000,
value: 1735682600000,
noPickBeforeStamp: 1735682600000,
noPickAfterStamp: 1735689600000,
inpClass: "text-center",
@ -42,7 +42,7 @@ import "@assets/css/flatpickr.dark.css";
@param {string} name - The name of the field. Case no label, this is the fallback. Can be a translation key or by default raw text.
@param {array} popovers - List of popovers to display more information
@param {string} [inpType="datepicker"] - The type of the field, useful when we have multiple fields in the same container to display the right field
@param {string|number|date} [defaultDate=null] - Default date when instanciate
@param {string|number|date} [value=""] - Default date when instanciate
@param {string|number} [noPickBeforeStamp=""] - Impossible to pick a date before this date
@param {string|number} [noPickAfterStamp=""] - Impossible to pick a date after this date
@param {boolean} [hideLabel=false]
@ -69,6 +69,11 @@ const props = defineProps({
type: String,
required: false,
},
value: {
type: [String, Number, Date],
required: false,
default: "",
},
popovers: {
type: Array,
required: false,
@ -98,7 +103,6 @@ const props = defineProps({
required: false,
default: "",
},
columns: {
type: [Object, Boolean],
required: false,
@ -112,11 +116,6 @@ const props = defineProps({
type: Boolean,
required: false,
},
defaultDate: {
type: [String, Number, Date],
required: false,
default: null,
},
// Impossible to pick a date before this date
noPickBeforeStamp: {
type: [String, Number],
@ -139,6 +138,7 @@ const props = defineProps({
const date = reactive({
isValid: true,
format: "m/d/Y H:i:S",
currStamp: "",
});
const picker = reactive({
@ -150,7 +150,7 @@ onMounted(() => {
datepicker = flatpickr(`#${props.id}`, {
locale: "en",
dateFormat: date.format,
defaultDate: props.defaultDate || "",
defaultDate: props.value,
enableTime: true,
enableSeconds: true,
time_24hr: true,
@ -160,6 +160,7 @@ onMounted(() => {
//Check if date is in interval
try {
const currStamp = Date.parse(dateStr);
date.currStamp = currStamp;
// Check pick is before min allow
if (props.noPickBeforeStamp && currStamp < props.noPickBeforeStamp) {
return instance.setDate(props.noPickBeforeStamp);
@ -652,6 +653,7 @@ function setIndex(calendarEl, tabindex) {
<div class="relative flex flex-col items-start">
<input
:data-timestamp="date.currStamp"
:tabindex="props.tabId"
:aria-controls="props.id"
:aria-selected="picker.isOpen ? 'true' : 'false'"

View file

@ -260,7 +260,7 @@ const emits = defineEmits(["inp"]);
:headerClass="props.headerClass"
/>
<select :name="props.name" class="hidden">
<select aria-hidden="true" :name="props.name" class="hidden">
<option
v-for="(value, id) in props.values"
:key="id"
@ -341,6 +341,8 @@ const emits = defineEmits(["inp"]);
: '',
'select-dropdown-btn',
]"
:data-setting-id="props.id"
:data-setting-value="value"
:aria-controls="`${props.id}-text`"
:aria-checked="
(select.value && select.value === value) ||

View file

@ -109,7 +109,7 @@ const data = {
type: "text",
containerClass: "z-32",
pattern: "^(\\/[\\-\\w.\\s]+)*\\/$",
inpType: "input",
inpType: "datepicker",
name: "nginx prefix",
columns: {
pc: 4,