remain multiples and advanced form

* fix vuln braces
* show multiples in reverse order for better UX
* created a advanced store for a better advanced form management
This commit is contained in:
Jordan Blasenhauer 2024-07-15 16:15:09 +02:00
parent 93ecef0373
commit 3c38278f3a
11 changed files with 503 additions and 357 deletions

View file

@ -4796,7 +4796,8 @@
"text": "Custom header to add (HeaderName: HeaderValue)."
}
],
"containerClass": "z-13"
"containerClass": "z-13",
"plugin_id": "headers"
}
},
"cookie-flags": {
@ -4829,7 +4830,8 @@
"text": "Cookie flags automatically added to all cookies (value accepted for nginx_cookie_flag_module)."
}
],
"containerClass": "z-12"
"containerClass": "z-12",
"plugin_id": "headers"
}
}
},
@ -5173,7 +5175,8 @@
"text": "URL (PCRE regex) where the limit request will be applied or special value / for all requests."
}
],
"containerClass": "z-6"
"containerClass": "z-6",
"plugin_id": "limit"
},
"LIMIT_REQ_RATE": {
"context": "multisite",
@ -5204,7 +5207,8 @@
"text": "Rate to apply to the URL (s for second, m for minute, h for hour and d for day)."
}
],
"containerClass": "z-5"
"containerClass": "z-5",
"plugin_id": "limit"
}
}
},
@ -7540,7 +7544,8 @@
"text": "Full URL of the proxied resource (proxy_pass)."
}
],
"containerClass": "z-26"
"containerClass": "z-26",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_URL": {
"context": "multisite",
@ -7571,7 +7576,8 @@
"text": "Location URL that will be proxied."
}
],
"containerClass": "z-25"
"containerClass": "z-25",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_WS": {
"context": "multisite",
@ -7602,7 +7608,8 @@
"text": "Enable websocket on the proxied resource."
}
],
"containerClass": "z-24"
"containerClass": "z-24",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_HEADERS": {
"context": "multisite",
@ -7633,7 +7640,8 @@
"text": "List of HTTP headers to send to proxied resource separated with semicolons (values for proxy_set_header directive)."
}
],
"containerClass": "z-23"
"containerClass": "z-23",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_HEADERS_CLIENT": {
"context": "multisite",
@ -7664,7 +7672,8 @@
"text": "List of HTTP headers to send to client separated with semicolons (values for add_header directive)."
}
],
"containerClass": "z-22"
"containerClass": "z-22",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_BUFFERING": {
"context": "multisite",
@ -7695,7 +7704,8 @@
"text": "Enable or disable buffering of responses from proxied resource."
}
],
"containerClass": "z-21"
"containerClass": "z-21",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_KEEPALIVE": {
"context": "multisite",
@ -7726,7 +7736,8 @@
"text": "Enable or disable keepalive connections with the proxied resource."
}
],
"containerClass": "z-20"
"containerClass": "z-20",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_AUTH_REQUEST": {
"context": "multisite",
@ -7757,7 +7768,8 @@
"text": "Enable authentication using an external provider (value of auth_request directive)."
}
],
"containerClass": "z-19"
"containerClass": "z-19",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_AUTH_REQUEST_SIGNIN_URL": {
"context": "multisite",
@ -7788,7 +7800,8 @@
"text": "Redirect clients to sign-in URL when using REVERSE_PROXY_AUTH_REQUEST (used when auth_request call returned 401)."
}
],
"containerClass": "z-18"
"containerClass": "z-18",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_AUTH_REQUEST_SET": {
"context": "multisite",
@ -7819,7 +7832,8 @@
"text": "List of variables to set from the authentication provider, separated with semicolons (values of auth_request_set directives)."
}
],
"containerClass": "z-17"
"containerClass": "z-17",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_CONNECT_TIMEOUT": {
"context": "multisite",
@ -7850,7 +7864,8 @@
"text": "Timeout when connecting to the proxied resource."
}
],
"containerClass": "z-16"
"containerClass": "z-16",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_READ_TIMEOUT": {
"context": "multisite",
@ -7881,7 +7896,8 @@
"text": "Timeout when reading from the proxied resource."
}
],
"containerClass": "z-15"
"containerClass": "z-15",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_SEND_TIMEOUT": {
"context": "multisite",
@ -7912,7 +7928,8 @@
"text": "Timeout when sending to the proxied resource."
}
],
"containerClass": "z-14"
"containerClass": "z-14",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_INCLUDES": {
"context": "multisite",
@ -7943,7 +7960,8 @@
"text": "Additional configuration to include in the location block, separated with spaces."
}
],
"containerClass": "z-13"
"containerClass": "z-13",
"plugin_id": "reverseproxy"
}
}
},
@ -7984,6 +8002,7 @@
}
],
"containerClass": "z-26",
"plugin_id": "reverseproxy",
"method": "scheduler"
},
"REVERSE_PROXY_URL_1": {
@ -8015,7 +8034,8 @@
"text": "Location URL that will be proxied."
}
],
"containerClass": "z-25"
"containerClass": "z-25",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_WS_1": {
"context": "multisite",
@ -8046,7 +8066,8 @@
"text": "Enable websocket on the proxied resource."
}
],
"containerClass": "z-24"
"containerClass": "z-24",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_HEADERS_1": {
"context": "multisite",
@ -8077,7 +8098,8 @@
"text": "List of HTTP headers to send to proxied resource separated with semicolons (values for proxy_set_header directive)."
}
],
"containerClass": "z-23"
"containerClass": "z-23",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_HEADERS_CLIENT_1": {
"context": "multisite",
@ -8108,7 +8130,8 @@
"text": "List of HTTP headers to send to client separated with semicolons (values for add_header directive)."
}
],
"containerClass": "z-22"
"containerClass": "z-22",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_BUFFERING_1": {
"context": "multisite",
@ -8139,7 +8162,8 @@
"text": "Enable or disable buffering of responses from proxied resource."
}
],
"containerClass": "z-21"
"containerClass": "z-21",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_KEEPALIVE_1": {
"context": "multisite",
@ -8170,7 +8194,8 @@
"text": "Enable or disable keepalive connections with the proxied resource."
}
],
"containerClass": "z-20"
"containerClass": "z-20",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_AUTH_REQUEST_1": {
"context": "multisite",
@ -8201,7 +8226,8 @@
"text": "Enable authentication using an external provider (value of auth_request directive)."
}
],
"containerClass": "z-19"
"containerClass": "z-19",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_AUTH_REQUEST_SIGNIN_URL_1": {
"context": "multisite",
@ -8232,7 +8258,8 @@
"text": "Redirect clients to sign-in URL when using REVERSE_PROXY_AUTH_REQUEST (used when auth_request call returned 401)."
}
],
"containerClass": "z-18"
"containerClass": "z-18",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_AUTH_REQUEST_SET_1": {
"context": "multisite",
@ -8263,7 +8290,8 @@
"text": "List of variables to set from the authentication provider, separated with semicolons (values of auth_request_set directives)."
}
],
"containerClass": "z-17"
"containerClass": "z-17",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_CONNECT_TIMEOUT_1": {
"context": "multisite",
@ -8294,7 +8322,8 @@
"text": "Timeout when connecting to the proxied resource."
}
],
"containerClass": "z-16"
"containerClass": "z-16",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_READ_TIMEOUT_1": {
"context": "multisite",
@ -8325,7 +8354,8 @@
"text": "Timeout when reading from the proxied resource."
}
],
"containerClass": "z-15"
"containerClass": "z-15",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_SEND_TIMEOUT_1": {
"context": "multisite",
@ -8356,7 +8386,8 @@
"text": "Timeout when sending to the proxied resource."
}
],
"containerClass": "z-14"
"containerClass": "z-14",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_INCLUDES_1": {
"context": "multisite",
@ -8387,7 +8418,8 @@
"text": "Additional configuration to include in the location block, separated with spaces."
}
],
"containerClass": "z-13"
"containerClass": "z-13",
"plugin_id": "reverseproxy"
}
},
"0": {
@ -8421,6 +8453,7 @@
}
],
"containerClass": "z-26",
"plugin_id": "reverseproxy",
"method": "ui"
},
"REVERSE_PROXY_URL": {
@ -8452,7 +8485,8 @@
"text": "Location URL that will be proxied."
}
],
"containerClass": "z-25"
"containerClass": "z-25",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_WS": {
"context": "multisite",
@ -8483,7 +8517,8 @@
"text": "Enable websocket on the proxied resource."
}
],
"containerClass": "z-24"
"containerClass": "z-24",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_HEADERS": {
"context": "multisite",
@ -8514,7 +8549,8 @@
"text": "List of HTTP headers to send to proxied resource separated with semicolons (values for proxy_set_header directive)."
}
],
"containerClass": "z-23"
"containerClass": "z-23",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_HEADERS_CLIENT": {
"context": "multisite",
@ -8545,7 +8581,8 @@
"text": "List of HTTP headers to send to client separated with semicolons (values for add_header directive)."
}
],
"containerClass": "z-22"
"containerClass": "z-22",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_BUFFERING": {
"context": "multisite",
@ -8576,7 +8613,8 @@
"text": "Enable or disable buffering of responses from proxied resource."
}
],
"containerClass": "z-21"
"containerClass": "z-21",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_KEEPALIVE": {
"context": "multisite",
@ -8607,7 +8645,8 @@
"text": "Enable or disable keepalive connections with the proxied resource."
}
],
"containerClass": "z-20"
"containerClass": "z-20",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_AUTH_REQUEST": {
"context": "multisite",
@ -8638,7 +8677,8 @@
"text": "Enable authentication using an external provider (value of auth_request directive)."
}
],
"containerClass": "z-19"
"containerClass": "z-19",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_AUTH_REQUEST_SIGNIN_URL": {
"context": "multisite",
@ -8669,7 +8709,8 @@
"text": "Redirect clients to sign-in URL when using REVERSE_PROXY_AUTH_REQUEST (used when auth_request call returned 401)."
}
],
"containerClass": "z-18"
"containerClass": "z-18",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_AUTH_REQUEST_SET": {
"context": "multisite",
@ -8700,7 +8741,8 @@
"text": "List of variables to set from the authentication provider, separated with semicolons (values of auth_request_set directives)."
}
],
"containerClass": "z-17"
"containerClass": "z-17",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_CONNECT_TIMEOUT": {
"context": "multisite",
@ -8731,7 +8773,8 @@
"text": "Timeout when connecting to the proxied resource."
}
],
"containerClass": "z-16"
"containerClass": "z-16",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_READ_TIMEOUT": {
"context": "multisite",
@ -8762,7 +8805,8 @@
"text": "Timeout when reading from the proxied resource."
}
],
"containerClass": "z-15"
"containerClass": "z-15",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_SEND_TIMEOUT": {
"context": "multisite",
@ -8793,7 +8837,8 @@
"text": "Timeout when sending to the proxied resource."
}
],
"containerClass": "z-14"
"containerClass": "z-14",
"plugin_id": "reverseproxy"
},
"REVERSE_PROXY_INCLUDES": {
"context": "multisite",
@ -8824,7 +8869,8 @@
"text": "Additional configuration to include in the location block, separated with spaces."
}
],
"containerClass": "z-13"
"containerClass": "z-13",
"plugin_id": "reverseproxy"
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
<script setup>
import { defineProps, reactive, onMounted, onUnmounted } from "vue";
import { defineProps, reactive, onMounted, onUnmounted, computed } from "vue";
import MessageUnmatch from "@components/Message/Unmatch.vue";
import Container from "@components/Widget/Container.vue";
import Fields from "@components/Form/Fields.vue";
@ -11,14 +11,8 @@ import Text from "@components/Widget/Text.vue";
import Filter from "@components/Widget/Filter.vue";
import GroupMultiple from "@components/Forms/Group/Multiple.vue";
import { plugin_types } from "@utils/variables";
import {
useCheckPluginsValidity,
useUpdateTemp,
useListenTempFields,
useUnlistenTempFields,
useDelAdvancedMult,
useAddAdvancedMult,
} from "@utils/form.js";
import { useAdvancedForm } from "@store/advanced.js";
import { useCheckPluginsValidity } from "@utils/form.js";
import { v4 as uuidv4 } from "uuid";
/**
@name Form/Advanced.vue
@ -53,6 +47,8 @@ import { v4 as uuidv4 } from "uuid";
@param {object} columns - Columns object.
*/
const advancedForm = useAdvancedForm();
const props = defineProps({
// id && value && method
template: {
@ -75,13 +71,11 @@ const props = defineProps({
const data = reactive({
currPlugin: "",
plugins: [],
base: JSON.parse(JSON.stringify(props.template)),
base: props.template,
isRegErr: false,
isReqErr: false,
settingErr: "",
pluginErr: "",
// Add filtering and check validity with regex and required
format: JSON.parse(JSON.stringify(props.template)),
});
const comboboxPlugin = {
@ -109,7 +103,7 @@ const buttonSave = {
size: "normal",
type: "button",
attrs: {
"data-submit-form": JSON.stringify(data.base),
"data-submit-form": JSON.stringify(advancedForm.templateBase),
},
containerClass: "flex justify-center",
iconName: "plus",
@ -201,19 +195,19 @@ const filters = [
];
function filter(filterData) {
advancedForm.templateUIFormat = filterData;
setValidity();
data.format = filterData;
data.plugins = getPluginNames(filterData);
data.plugins = getPluginNames(advancedForm.templateUIFormat);
// Check after a filter if previous plugin is still in the list and if at least one plugin is available
// Update if not the case
data.currPlugin = data.plugins.includes(data.currPlugin)
? data.currPlugin
: getFirstPlugin(filterData);
: getFirstPlugin(advancedForm.templateUIFormat);
}
function setValidity() {
const [isRegErr, isReqErr, settingErr, settingNameErr, pluginErr, id] =
useCheckPluginsValidity(data.base);
useCheckPluginsValidity(advancedForm.templateUI);
data.isRegErr = isRegErr;
data.isReqErr = isReqErr;
data.settingErr = settingErr;
@ -242,24 +236,20 @@ function getPluginNames(template) {
}
}
function updateTemplate(e) {
if (!e.target.closest("[data-advanced-form-plugin]")) return;
useUpdateTemp(e, data.base);
}
onMounted(() => {
advancedForm.setTemplate(props.template);
// Get first props.forms template name
data.currPlugin = getFirstPlugin(props.template);
data.plugins = getPluginNames(props.template);
data.currPlugin = getFirstPlugin(advancedForm.templateUIFormat);
data.plugins = getPluginNames(advancedForm.templateUIFormat);
setValidity();
// Store update data on
// I want updatInp to access event, data.base and the container attribut
useListenTempFields(updateTemplate);
advancedForm.useListenTempFields();
});
onUnmounted(() => {
useUnlistenTempFields(updateTemplate);
advancedForm.useUnlistenTempFields();
});
</script>
@ -277,7 +267,7 @@ onUnmounted(() => {
<Filter
v-if="filters.length"
@filter="(v) => filter(v)"
:data="data.base"
:data="advancedForm.templateUI"
:filters="filters"
>
<Combobox
@ -287,8 +277,8 @@ onUnmounted(() => {
@inp="data.currPlugin = $event"
/>
</Filter>
<MessageUnmatch v-if="!data.format.length" />
<template v-for="plugin in data.format">
<MessageUnmatch v-if="!advancedForm.templateUIFormat.length" />
<template v-for="(plugin, pluginId) in advancedForm.templateUIFormat">
<Container
data-is="content"
data-advanced-form-plugin
@ -309,24 +299,23 @@ onUnmounted(() => {
<GroupMultiple
@delete="
(multName, groupName) =>
useDelAdvancedMult(data.base, multName, groupName)
advancedForm.delMultiple(plugin.id, multName, groupName)
"
@add="(multName) => useAddAdvancedMult(data.base, multName)"
v-if="plugin.multiples"
@add="(multName) => advancedForm.addMultiple(plugin.id, multName)"
:multiples="plugin.multiples"
/>
</Container>
</template>
<Button
v-if="data.format.length"
v-if="advancedForm.templateUIFormat.length"
v-bind="buttonSave"
:disabled="data.isReqErr || data.isRegErr ? true : false"
/>
<div class="flex justify-center items-center" data-is="form-error">
<Text
v-if="
(data.format.length && data.isRegErr) ||
(data.format.length && data.isReqErr)
(advancedForm.templateUIFormat.length && data.isRegErr) ||
(advancedForm.templateUIFormat.length && data.isReqErr)
"
:text="
data.isReqErr

View file

@ -7,12 +7,7 @@ import Subtitle from "@components/Widget/Subtitle.vue";
import Button from "@components/Widget/Button.vue";
import Text from "@components/Widget/Text.vue";
import { v4 as uuidv4 } from "uuid";
import {
useCheckPluginsValidity,
useUpdateTemp,
useListenTempFields,
useUnlistenTempFields,
} from "@utils/form.js";
import { useCheckPluginsValidity } from "@utils/form.js";
/**
@name Form/Easy.vue

View file

@ -1,5 +1,12 @@
<script setup>
import { reactive, defineProps, defineEmits } from "vue";
import {
reactive,
defineProps,
defineEmits,
watch,
computed,
onMounted,
} from "vue";
import { contentIndex } from "@utils/tabindex.js";
import ButtonGroup from "@components/Widget/ButtonGroup.vue";
import Button from "@components/Widget/Button.vue";
@ -205,6 +212,9 @@ const buttonDelete = {
containerClass: "flex justify-center",
};
// emits
const emits = defineEmits(["delete", "add"]);
// Check if at least one input is disabled (this means a method different than ui, default or manual is used)
// If true, disable the delete button
function setDeleteState(group) {
@ -230,19 +240,16 @@ function delInvisible(id) {
}
function toggleVisible(id) {
if (multiples.invisible.includes(id)) {
delInvisible(id);
} else {
setInvisible(id);
}
multiples.invisible.includes(id) ? delInvisible(id) : setInvisible(id);
}
// emits
const emits = defineEmits(["delete", "add"]);
function delGroup(multName, groupName) {
emits("delete", multName, groupName);
}
</script>
<template>
<template :key="multName" v-for="(multObj, multName, id) in props.multiples">
<template v-for="(multObj, multName, id) in props.multiples">
<Container
data-is="multiple"
class="layout-settings-multiple"
@ -264,36 +271,43 @@ const emits = defineEmits(["delete", "add"]);
</div>
</Container>
<template
:key="groupId"
v-for="(group, groupName, groupId) in props.multiples[multName]"
<div
:aria-hidden="
multiples.invisible.includes(`${multName}${id}`) ? 'false' : 'true'
"
:class="[
'flex-col-reverse col-span-12',
multiples.invisible.includes(`${multName}${id}`) ? 'hidden' : 'flex',
]"
>
<Container
data-group="multiple"
:data-group-name="groupName"
:data-mult-name="multName"
:data-plugin-name="multName"
class="layout-settings-multiple-group"
:aria-hidden="multiples.invisible.includes(`${multName}${id}`)"
v-show="
multiples.invisible.includes(`${multName}${id}`) ? false : true
"
<template
:key="groupName"
v-for="(group, groupName, groupId) in props.multiples[multName]"
>
<Subtitle
:subtitle="`${multName.replaceAll('-', ' ')} #${+groupName + 1}`"
/>
<template
:key="setting"
v-for="(setting, settingName, settingId) in group"
<Container
data-group="multiple"
:data-group-name="groupName"
:data-mult-name="multName"
class="layout-settings-multiple-group"
>
<Fields :setting="setting" />
</template>
<ButtonGroup
@click="$emit('delete', multName, groupName)"
:buttons="[setDeleteState(group)]"
/>
</Container>
</template>
<Subtitle
:subtitle="`${multName.replaceAll('-', ' ')} #${+groupName + 1}`"
/>
<template
:key="settingName"
v-for="(setting, settingName, settingId) in group"
>
<Fields :setting="setting" />
</template>
<div class="col-span-12 flex justify-center">
<Button
@click="delGroup(multName, groupName)"
v-bind="setDeleteState(group)"
/>
</div>
</Container>
</template>
</div>
</Container>
</template>
</template>

View file

@ -75,9 +75,13 @@ const filters = reactive({
isFiltering: false,
});
watch(props.data, () => {
filterData();
});
watch(
() => props.data,
() => {
filterData();
},
{ deep: true }
);
function startFilter(filter = {}, value) {
// Case we have new filter value, update it
@ -133,7 +137,6 @@ function startFilter(filter = {}, value) {
// Remove empty row
template = template.filter((row) => row.length > 0);
}
emits("filter", template);
}

View file

@ -0,0 +1,285 @@
import { defineStore } from "pinia";
import { ref } from "vue";
/**
@name useAdvancedForm
@description Store to share the template and others form data.
*/
export const useAdvancedForm = defineStore("advanced", () => {
// Default template, don't touch it. It will be used to reset the template.
const template = ref({});
// Base template will keep every data (data that doesn't need to be render on UI but need to be there for backend).
const templateBase = ref({});
// UI template will keep the data that will be render on UI.
const templateUI = ref({});
// UI template will keep the data that will be render on UI with additionnal format like filtering.
const templateUIFormat = ref({});
const updateCount = ref(0);
/**
@name setTemplate
@description Set the template we are going to use to generate the form and update it (like adding multiples).
@param template - Template with plugins list and detail settings
*/
function setTemplate(template) {
template.value = template;
templateBase.value = template;
templateUI.value = JSON.parse(JSON.stringify(template));
templateUIFormat.value = JSON.parse(JSON.stringify(template));
}
/**
@name delMultiple
@description This function will delete a group of multiples in the template.
The way the backend is working is that to delete a group, we need to send the group name with all default values.
This function needs to be call from the multiples component parent with the template and the group name to delete.
We will update the values of the group to default values.
@param pluginId - id of the plugin on the template array.
@param multName - Input id to update
@param groupName - Input value to update
*/
function delMultiple(pluginId, multName, groupName) {
// Get the index of plugin using pluginId
const index = templateBase.value.findIndex(
(plugin) => plugin.id === pluginId
);
// For back end, we need to keep the group but updating values to default in order to delete it
for (const [key, value] of Object.entries(
templateBase.value[index].multiples[multName][groupName]
)) {
value.value = value.default;
}
// For UI, we can delete the group to avoid rendering it
delete templateUI.value[index].multiples[multName][groupName];
updateCount.value++;
}
/**
@name addMultiple
@description This function will add a group of multiple in the template with default values.
Each plugin has a key "multiples_schema" with each multiples group and their default values.
We will retrieve the wanted multiple group and add it on the "multiples" key that contains the multiples that apply to the plugin.
@param pluginId - id of the plugin on the template array.
@param multName - multiple group name to add
*/
function addMultiple(pluginId, multName) {
// Get the index of plugin using pluginId
const index = templateBase.value.findIndex(
(plugin) => plugin.id === pluginId
);
// Get the right multiple schema
const multipleSchema = JSON.parse(
JSON.stringify(templateBase.value[index]?.multiples_schema[multName])
);
const newMultiple = {};
// Get the highest id in Object.keys(plugin?.multiples[multName])
const nextGroupId =
Math.max(...Object.keys(templateBase.value[index]?.multiples[multName])) +
1;
// Set the default values as value
for (const [key, value] of Object.entries(multipleSchema)) {
value.value = value.default;
newMultiple[`${key}${nextGroupId > 0 ? "_" + nextGroupId : ""}`] = value;
}
// Add new group as first key of plugin.multiples.multName
templateBase.value[index].multiples[multName][nextGroupId] = newMultiple;
// We need to show the new group on UI too
templateUI.value[index].multiples[multName][nextGroupId] = newMultiple;
updateCount.value++;
}
/**
@name useListenTempFields
@description This will add an handler to all needed event listeners to listen to input, select... fields in order to update the template settings.
@example
function hander(e) {
// some code before calling useUpdateTemp
if (!e.target.closest("[data-advanced-form-plugin]")) return;
useUpdateTemp(e, data.base);
}
*/
function useListenTempFields() {
window.addEventListener("input", useUpdateTemp);
window.addEventListener("change", useUpdateTemp);
window.addEventListener("click", useUpdateTemp);
}
/**
@name useUnlistenTempFields
@description This will stop listening to input, select... fields. Performance optimization and avoid duplicate calls conflicts.
@example
function hander(e) {
// some code before calling useUpdateTemp
if (!e.target.closest("[data-advanced-form-plugin]")) return;
useUpdateTemp(e, data.base);
}
*/
function useUnlistenTempFields() {
window.removeEventListener("change", useUpdateTemp);
window.removeEventListener("click", useUpdateTemp);
}
/**
@name useUpdateTemp
@description This function will check if the target is a setting input-like field.
In case it is, it will get the id and value for each field case, this will allow to update the template settings.
@example
template = [
{
name: "test",
settings: {
test: {
required: true,
value: "",
pattern: "^[a-zA-Z0-9]*$",
},
},
},
];
@param e - Event object, get it by default in the event listener.
@param templates - Array of templates to update
*/
function useUpdateTemp(e, templates) {
templates = [templateBase.value, templateUI.value];
// Filter to avoid multiple calls
if (!e.target.closest("[data-advanced-form-plugin]")) return;
if (e.type === "change" && e.target.tagName === "INPUT") 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;
// update settings
useUpdateTempSettings(templates, inpId, inpValue, e.target);
useUpdateTempMultiples(templates, inpId, inpValue, e.target);
}, 50);
}
/**
@name useUpdateTempSettings
@description This function will loop on template settings in order to update the setting value.
This will check each plugin.settings (what I call regular) instead of other type of settings like multiples (in plugin.multiples).
This function needs to be call in useUpdateTemp.
@param templates - Templates array with plugins list and detail settings
@param inpId - Input id to update
@param inpValue - Input value to update
*/
function useUpdateTempSettings(templates, inpId, inpValue, target) {
// Case get data-group attribut, this is not a regular setting
if (target.closest("[data-group]")) return;
for (let i = 0; i < templates.length; i++) {
const template = templates[i];
// Try to update settings
let isSettingUpdated = false;
for (let i = 0; i < template.length; i++) {
const plugin = template[i];
const settings = plugin?.settings;
if (!settings) continue;
for (const [key, value] of Object.entries(settings)) {
if (value.id === inpId) {
value.value = inpValue;
isSettingUpdated = true;
break;
}
}
if (isSettingUpdated) break;
}
}
}
/**
@name useUpdateTempMultiples
@description This function will loop on template multiples in order to update the setting value.
This will check each plugin.multiples that can be found in the template.
This function needs to be call in useUpdateTemp.
@param templates - Templates array with plugins list and detail settings
@param inpId - Input id to update
@param inpValue - Input value to update
*/
function useUpdateTempMultiples(templates, inpId, inpValue, target) {
// Case get data-group attribut, this is not a regular setting
if (!target.closest("[data-group='multiple']")) return;
const multName =
target
.closest("[data-group='multiple']")
.getAttribute("data-mult-name") || "";
const groupName =
target
.closest("[data-group='multiple']")
.getAttribute("data-group-name") || "";
for (let i = 0; i < templates.length; i++) {
const template = templates[i];
// Check at the same time the inpId without prefix group he is part of
// And try to update an existing inpId
// Case we found the inpId, we update the value
// Case we didn't find existing inpId, we create a new one
let isSettingUpdated = false;
for (let i = 0; i < template.length; i++) {
const plugin = template[i];
const multiples = plugin?.multiples;
// Case no multiples, continue
if (!multiples || Object.keys(multiples).length <= 0) continue;
// Check if can find mult name in multiples
if (!(multName in multiples)) continue;
// Check if can find group name in multiples
if (!(groupName in multiples[multName])) continue;
const settings = multiples[multName][groupName];
for (const [key, value] of Object.entries(settings)) {
if (value.id !== inpId) continue;
value.value = inpValue;
isSettingUpdated = true;
break;
}
if (isSettingUpdated) break;
}
}
}
/**
@name $reset
@description Will reset the template to the original one using the default template. The default template need to be set once.
*/
function $reset() {
templateBase.value = template.value;
templateUI.value = template.value;
updateCount.value++;
}
return {
templateBase,
templateUI,
templateUIFormat,
setTemplate,
addMultiple,
delMultiple,
useListenTempFields,
useUnlistenTempFields,
$reset,
};
});

View file

@ -302,232 +302,10 @@ function useCheckPluginsValidity(template) {
return [isRegErr, isReqErr, settingErr, settingNameErr, pluginErr, id];
}
/**
@name useListenTempFields
@description This will add an handler to all needed event listeners to listen to input, select... fields in order to update the template settings.
@example
function hander(e) {
// some code before calling useUpdateTemp
if (!e.target.closest("[data-advanced-form-plugin]")) return;
useUpdateTemp(e, data.base);
}
@param handler - Callback function to call when event is triggered. This is usually an intermediate function that will call the useUpdateTemp function.
*/
function useListenTempFields(handler) {
window.addEventListener("input", handler);
window.addEventListener("change", handler);
window.addEventListener("click", handler);
}
/**
@name useUnlistenTempFields
@description This will stop listening to input, select... fields. Performance optimization and avoid duplicate calls conflicts.
@example
function hander(e) {
// some code before calling useUpdateTemp
if (!e.target.closest("[data-advanced-form-plugin]")) return;
useUpdateTemp(e, data.base);
}
@param handler - Callback function to call when event is triggered. Need to be the same function as the one passed to useListenTempFields.
*/
function useUnlistenTempFields(handler) {
window.removeEventListener("input", handler);
window.removeEventListener("change", handler);
window.removeEventListener("click", handler);
}
/**
@name useUpdateTemp
@description This function will check if the target is a setting input-like field.
In case it is, it will get the id and value for each field case, this will allow to update the template settings.
@example
template = [
{
name: "test",
settings: {
test: {
required: true,
value: "",
pattern: "^[a-zA-Z0-9]*$",
},
},
},
];
@param e - Event object, get it by default in the event listener.
@param template - Template with plugins list and detail settings
*/
function useUpdateTemp(e, template) {
// 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;
// update settings
useUpdateTempSettings(template, inpId, inpValue, e.target);
useUpdateTempMultiples(template, inpId, inpValue, e.target);
return template;
}, 50);
}
/**
@name useUpdateTempSettings
@description This function will loop on template settings in order to update the setting value.
This will check each plugin.settings (what I call regular) instead of other type of settings like multiples (in plugin.multiples).
This function needs to be call in useUpdateTemp.
@param template - Template with plugins list and detail settings
@param inpId - Input id to update
@param inpValue - Input value to update
*/
function useUpdateTempSettings(template, inpId, inpValue, target) {
// Case get data-group attribut, this is not a regular setting
if (target.closest("[data-group]")) return;
// Try to update settings
let isSettingUpdated = false;
for (let i = 0; i < template.length; i++) {
const plugin = template[i];
const settings = plugin?.settings;
if (!settings) continue;
for (const [key, value] of Object.entries(settings)) {
if (value.id === inpId) {
value.value = inpValue;
isSettingUpdated = true;
break;
}
}
if (isSettingUpdated) break;
}
}
/**
@name useUpdateTempMultiples
@description This function will loop on template multiples in order to update the setting value.
This will check each plugin.multiples that can be found in the template.
This function needs to be call in useUpdateTemp.
@param template - Template with plugins list and detail settings
@param inpId - Input id to update
@param inpValue - Input value to update
*/
function useUpdateTempMultiples(template, inpId, inpValue, target) {
// Case get data-group attribut, this is not a regular setting
if (!target.closest("[data-group='multiple']")) return;
const multName =
target.closest("[data-group='multiple']").getAttribute("data-mult-name") ||
"";
const groupName =
target.closest("[data-group='multiple']").getAttribute("data-group-name") ||
"";
// Check at the same time the inpId without prefix group he is part of
// And try to update an existing inpId
// Case we found the inpId, we update the value
// Case we didn't find existing inpId, we create a new one
let isSettingUpdated = false;
for (let i = 0; i < template.length; i++) {
const plugin = template[i];
const multiples = plugin?.multiples;
// Case no multiples, continue
if (!multiples || Object.keys(multiples).length <= 0) continue;
// Check if can find mult name in multiples
if (!(multName in multiples)) continue;
// Check if can find group name in multiples
if (!(groupName in multiples[multName])) continue;
const settings = multiples[multName][groupName];
for (const [key, value] of Object.entries(settings)) {
if (value.id !== inpId) continue;
value.value = inpValue;
isSettingUpdated = true;
break;
}
if (isSettingUpdated) break;
}
}
/**
@name useDelAdvancedMult
@description This function will delete a group of multiples in the template.
The way the backend is working is that to delete a group, we need to send the group name with all default values.
This function needs to be call from the multiples component parent with the template and the group name to delete.
We will update the values of the group to default values.
@param template - Template with plugins list and detail settings
@param multName - Input id to update
@param groupName - Input value to update
*/
function useDelAdvancedMult(template, multName, groupName) {
for (let i = 0; i < template.length; i++) {
const plugin = template[i];
const multiples = plugin?.multiples;
if (!multiples) continue;
if (!(multName in multiples)) continue;
if (!(groupName in multiples[multName])) continue;
delete multiples[multName][groupName];
return;
}
}
/**
@name useAddAdvancedMult
@description This function will add a group of multiple in the template with default values.
Each plugin has a key "multiples_schema" with each multiples group and their default values.
We will retrieve the wanted multiple group and add it on the "multiples" key that contains the multiples that apply to the plugin.
@param template - Template with plugins list and detail settings
@param multName - Input id to update
*/
function useAddAdvancedMult(template, multName) {
// Get the right multiple schema
let multipleSchema = {};
let plugin;
let nextGroupId;
for (let i = 0; i < template.length; i++) {
plugin = template[i];
const multiples = plugin?.multiples;
if (!multiples) continue;
if (!(multName in multiples)) continue;
multipleSchema = plugin?.multiples_schema[multName];
console.log(multipleSchema);
// Get the highest id in Object.keys(plugin?.multiples[multName])
nextGroupId = Math.max(...Object.keys(plugin?.multiples[multName])) + 1;
if (!multipleSchema) return;
break;
}
// Set the default values as value
for (const [key, value] of Object.entries(multipleSchema)) {
value.value = value.default;
}
// Add new group as first key of plugin.multiples.multName
plugin.multiples[multName][nextGroupId] = multipleSchema;
console.log(plugin.multiples[multName]);
}
export {
useForm,
useFilter,
isItemKeyword,
isItemSelect,
useCheckPluginsValidity,
useUpdateTemp,
useListenTempFields,
useUnlistenTempFields,
useDelAdvancedMult,
useAddAdvancedMult,
};

View file

@ -52,10 +52,31 @@ export default {
"sm:flex-col",
"md:flex-col",
"lg:flex-col",
"flex-col-reverse",
"sm:flex-col-reverse",
"md:flex-col-reverse",
"lg:flex-col-reverse",
"flex-row",
"sm:flex-row",
"md:flex-row",
"lg:flex-row",
"flex-row-reverse",
"sm:flex-row-reverse",
"md:flex-row-reverse",
"lg:flex-row-reverse",
"flex-wrap",
"sm:flex-wrap",
"md:flex-wrap",
"lg:flex-wrap",
"flex-wrap-reverse",
"sm:flex-wrap-reverse",
"md:flex-wrap-reverse",
"lg:flex-wrap-reverse",
"flex-nowrap",
"sm:flex-nowrap",
"md:flex-nowrap",
"lg:flex-nowrap",
"justify-center",
"sm:justify-center",
"md:justify-center",
@ -216,6 +237,10 @@ export default {
"sm:m-2",
"md:m-2",
"lg:m-2",
"mx-2",
"sm:mx-2",
"md:mx-2",
"lg:mx-2",
"pl-2",
"sm:pl-2",
"md:pl-2",
@ -236,6 +261,10 @@ export default {
"sm:p-2",
"md:p-2",
"lg:p-2",
"px-2",
"sm:px-2",
"md:px-2",
"lg:px-2",
"scale-90",
"scale-95",
"w-fit",

View file

@ -4,6 +4,7 @@
"requires": true,
"packages": {
"": {
"name": "ui",
"dependencies": {
"ace-builds": "^1.32.7",
"dompurify": "^3.0.9",
@ -167,11 +168,12 @@
}
},
"node_modules/braces": {
"version": "3.0.2",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@ -280,9 +282,10 @@
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
@ -1104,10 +1107,12 @@
}
},
"braces": {
"version": "3.0.2",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
}
},
"camelcase-css": {
@ -1184,7 +1189,9 @@
}
},
"fill-range": {
"version": "7.0.1",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"