add form logic + update build.js

* build.js will add csrf token input
* add a global submit form using attributs
* add JSdoc on utils
* remove useless backdrop event
This commit is contained in:
Jordan Blasenhauer 2024-06-06 10:35:56 +02:00
parent 4ddceff055
commit df1a17445c
9 changed files with 127 additions and 23 deletions

View file

@ -109,12 +109,16 @@ function setFlaskData() {
let attributs = "";
matches.forEach((match) => {
const matchFormat = match.replace('="', "").replace("='", "");
attributs += `<div class="hidden" ${matchFormat}={{${matchFormat}}}></div>\n`;
attributs += `<div class="hidden" ${matchFormat}={{${matchFormat.replaceAll(
"-",
"_"
)}}}></div>\n`;
});
// insert the new content
updateData =
data.slice(0, bodyIndex) +
`\n<body>\n` +
`<div class="hidden" data-csrf-token={{ csrf_token() }}></div>\n` +
attributs +
`<div id="app"></div>\n</body>\n</html>`;
} catch (e) {

View file

@ -11,7 +11,7 @@
<body>
<div class="hidden" data-server-global='{"username" : "admin"}'></div>
<div class="hidden"
data-server-flash='[{"type" : "success", "title" : "title", "message" : "Success feedback"}, {"type" : "error", "title" : "title", "message" : "Error feedback"}, {"type" : "warning", "title" : "title", "message" : "Warning feedback"}, {"type" : "info", "title" : "title", "message" : "Info feedback"}]'>
data-server-flash='[{"type" : "success", "title" : "success", "message" : "Success feedback"}, {"type" : "error", "title" : "error", "message" : "Error feedback"}, {"type" : "warning", "title" : "warning", "message" : "Warning feedback"}, {"type" : "info", "title" : "info", "message" : "Info feedback"}]'>
</div>
<div class="hidden"
data-server-builder='[{"type":"card", "link" : "/services", "containerClass":"","containerColumns":{"pc":4,"tablet":6,"mobile":12},"widgets":[{"type":"Stat", "link": "https://github.com/bunkerity/bunkerweb","data":{"title":"home_version","subtitle":"home_all_features_available","subtitleColor":"success","stat":"home_pro","iconName":"crown","iconColor":"amber"}}]},{"type":"card","containerClass":"","containerColumns":{"pc":4,"tablet":6,"mobile":12},"widgets":[{"type":"Stat","data":{"title":"home_version_number","subtitle":"home_latest_version","subtitleColor":"success","stat":"1.5.7","iconName":"wire","iconColor":"teal"}}]},{"type":"card","containerClass":"","containerColumns":{"pc":4,"tablet":6,"mobile":12},"widgets":[{"type":"Stat","data":{"title":"home_instances","subtitle":"home_total_number","subtitleColor":"info", "stat":"1","iconName":"box","iconColor":"dark"}}]}, {"type":"card","containerClass":"","containerColumns":{"pc":4,"tablet":6,"mobile":12},"widgets":[{"type":"Stat","data":{"title":"home_services","subtitle":"home_all_methods_included","subtitleColor":"info","stat":"2","iconName":"disk","iconColor":"orange"}}]}, {"type":"card","containerClass":"","containerColumns":{"pc":4,"tablet":6,"mobile":12},"widgets":[{"type":"Stat","data":{"title":"home_plugins","subtitle":"home_no_error","subtitleColor":"success","stat":"42","iconName":"puzzle","iconColor":"yellow"}}]}]'>

View file

@ -3,6 +3,7 @@ import { reactive, onBeforeMount, onMounted } from "vue";
import DashboardLayout from "@components/Dashboard/Layout.vue";
import Builder from "@components/Builder.vue";
import { useGlobal } from "@utils/global.js";
import { useForm } from "@utils/form.js";
/**
@name Page/Home.vue
@ -27,6 +28,7 @@ onBeforeMount(() => {
onMounted(() => {
useGlobal();
useForm();
});
// const data = [

View file

@ -14,7 +14,7 @@
data-server-flash='[{"type" : "success", "title" : "title", "message" : "Success feedback"}, {"type" : "error", "title" : "title", "message" : "Error feedback"}, {"type" : "warning", "title" : "title", "message" : "Warning feedback"}, {"type" : "info", "title" : "title", "message" : "Info feedback"}]'>
</div>
<div class="hidden"
data-server-builder='[{"type":"card","link":"/services","containerClass":"","containerColumns":{"pc":6,"tablet":6,"mobile":12},"widgets":[{"type":"Instance","data":{"details":[{"key":"instances_hostname","value":"www.example.com"},{"key":"instances_method","value":"dashboard_ui"},{"key":"instances_port","value":"1084"},{"key":"instances_status","value":"instances_active"}],"status":"success","title":"www.example.com","buttons":[{"attrs" : {"aria-controls" : "test-el", "aria-expanded" : "false"}, "text":"action_edit","color":"edit","size":"normal"}]}}]},{"type":"card","link":"/services","containerClass":"","containerColumns":{"pc":6,"tablet":6,"mobile":12},"widgets":[{"type":"Instance","data":{"details":[{"key":"instances_hostname","value":"www.example.com"},{"key":"instances_method","value":"dashboard_ui"},{"key":"instances_port","value":"1084"},{"key":"instances_status","value":"instances_active"}],"status":"error","title":"www.example.com","buttons":[{ "text":"action_start","color":"valid","size":"normal"}]}}]}]'>
data-server-builder='[{"type":"card","link":"/services","containerClass":"","containerColumns":{"pc":6,"tablet":6,"mobile":12},"widgets":[{"type":"Instance","data":{"details":[{"key":"instances_hostname","value":"www.example.com"},{"key":"instances_method","value":"dashboard_ui"},{"key":"instances_port","value":"1084"},{"key":"instances_status","value":"instances_active"}],"status":"success","title":"www.example.com","buttons":[{"attrs" : {"data-form-INSTANCE_ID" : "instance-1", "data-form-operation" : "edit", "data-submit-form" : "true"}, "text":"action_edit","color":"edit","size":"normal"}]}}]},{"type":"card","link":"/services","containerClass":"","containerColumns":{"pc":6,"tablet":6,"mobile":12},"widgets":[{"type":"Instance","data":{"details":[{"key":"instances_hostname","value":"www.example.com"},{"key":"instances_method","value":"dashboard_ui"},{"key":"instances_port","value":"1084"},{"key":"instances_status","value":"instances_active"}],"status":"error","title":"www.example.com","buttons":[{ "text":"action_start","color":"valid","size":"normal"}]}}]}]'>
</div>
<div id="app"></div>
<script type="module" src="instances.js"></script>

View file

@ -17,14 +17,3 @@ export const useBannerStore = defineStore("banner", () => {
return { isBanner, bannerClass, setBannerVisible };
});
/**
@name useBackdropStore
@description Store to share the current backdrop state (visible or not).
This backdrop avoid to click on the main content when we want to show a modal or a dialog.
*/
export const useBackdropStore = defineStore("backdrop", () => {
const clickCount = ref(0);
return { clickCount };
});

View file

@ -0,0 +1,76 @@
/**
@name utils/form.js
@description This file contains form utils that will be used in the application by default.
This file contains functions related to form validation, form submission, and other form utils.
*/
/**
@name useForm
@description This function is a composable wrapper that contains all the form utils functions.
This function will for example look for elements with data-submit-form attribute and submit the form with the data attributes.
*/
function useForm() {
window.addEventListener("click", (e) => {
if (!e.target.hasAttribute("data-submit-form")) return;
const data = useGetFormDataAttr(e.target);
useSubmitForm(data);
});
}
/**
@name useSubmitForm
@description Create programmatically a form element and submit it with the given data object of type {key: value}.
This will create a FormData and append data arguments to it, retrieve the csrf token and send it with a regular form.
@example
{
instance: "1",
operation: "delete",
}
@param {object} data - Object with the form data to submit.
*/
function useSubmitForm(data) {
// Create a form element
const form = document.createElement("form");
form.style.display = "none";
form.method = "POST";
// Retrieve csrf token form data-crfs-token
try {
const csrfToken = document.querySelector("[data-csrf-token]");
if (csrfToken) {
data.csrf_token = csrfToken.getAttribute("data-csrf-token");
}
} catch (e) {}
// Add input elements with the data object
for (const key in data) {
const input = document.createElement("input");
input.type = "hidden";
input.name = key;
input.value = data[key];
form.appendChild(input);
}
// Append the form to the body and submit it
document.querySelector("body").appendChild(form);
console.log(form);
form.submit();
}
/**
@name useGetFormDataAttr
@description /Get the form data store on attributes of the element.
Format is data-form-<key>="<value>"
@example document.querySelector("[data-submit-form]")
@param {DOMElement} el - Element to get the data attributes.
*/
function useGetFormDataAttr(el) {
const data = {};
const attributes = el.attributes;
for (let i = 0; i < attributes.length; i++) {
if (attributes[i].name.includes("data-form-")) {
const key = attributes[i].name.replace("data-form-", "");
data[key] = attributes[i].value;
}
}
return data;
}
export { useForm };

View file

@ -1,17 +1,24 @@
/**
@name global.js
@name utils/global.js
@description This file contains global utils that will be used in the application by default.
This file contains functions related to accessibilities, cookies, and other global utils.
*/
// This function is a wrapper that contains all the global utils functions.
/**
@name useGlobal
@description This function is a wrapper that contains all the global utils functions.
This function will for example update the aria-expanded attribute of an element in case we have an aria-controls attribute.
*/
function useGlobal() {
window.addEventListener("click", (e) => {
updateExpanded();
});
}
// This function updates the aria-expanded attribute of an element in case we have an aria-controls attribute.
/**
@name useGlobal
@description This function updates the aria-expanded attribute of an element in case we have an aria-controls attribute.
*/
function updateExpanded() {
// Wait for previous event and element visibility update
setTimeout(() => {
@ -35,7 +42,11 @@ function updateExpanded() {
}, 50);
}
// Check all the possible ways to hide an element
/**
@name isElHidden
@description This function is a util that checks if an element is hidden.
This will check for multiple ways to hide an element like aria-hidden, hidden class, display none, visibility hidden, and !hidden class.
*/
function isElHidden(el) {
return el.hasAttribute("aria-hidden")
? el.getAttribute("aria-hidden") === "true"

View file

@ -3,7 +3,7 @@ import en from "@lang/en.json" assert { type: "json" };
import fr from "@lang/fr.json" assert { type: "json" };
/**
@name lang.js
@name utils/lang.js
@description This file contains utils to manage the language of the application.
This is here that we retrieve json files to add translations.
This lang.js works with vue-i18n.
@ -12,10 +12,20 @@ import fr from "@lang/fr.json" assert { type: "json" };
const availablesLangs = ["en", "fr"];
/**
@name getAllLang
@description Return all the languages json data available in the application.
*/
function getAllLang() {
return { en: en, fr: fr };
}
/**
@name getAllLangCurrPage
@description Filter the needed translations for the current page in order to reduce the size of the i18n object.
@example ["dashboard", "settings", "profile"]
@param {array} pagesArr - Array of strings with the names of the prefixes of the translations needed.
*/
function getAllLangCurrPage(pagesArr) {
const langs = getAllLang();
// for each lang
@ -31,7 +41,13 @@ function getAllLangCurrPage(pagesArr) {
return langs;
}
export function getI18n(pagesArr = []) {
/**
@name getI18n
@description Return the i18n object with the translations needed for the current page for all available languages.
@example ["dashboard", "settings", "profile"]
@param {array} pagesArr - Array of strings with the names of the prefixes of the translations needed.
*/
function getI18n(pagesArr = []) {
const messages =
pagesArr.length > 0 ? getAllLangCurrPage(pagesArr) : getAllLang();
@ -48,7 +64,11 @@ export function getI18n(pagesArr = []) {
return i18n;
}
export function getLocalLang() {
/**
@name getLocalLang
@description This will return the user langage checking the store, the browser, or the default lang.
*/
function getLocalLang() {
// get store lang, or local, or default
if (
sessionStorage.getItem("lang") &&
@ -77,3 +97,5 @@ export function getLocalLang() {
return "en";
}
export { getI18n, getLocalLang };

View file

@ -1,10 +1,10 @@
/**
@name tabIndex.js
@name utils/tabIndex.js
@description This file contains the tab indexes for the main components of the application.
Tab indexes are used to navigate through the application using the keyboard.
This is useful for people with disabilities or people who prefer to use the keyboard instead of the mouse.
*/
const bannerIndex = "-1";
const menuIndex = "1";
const langIndex = "2";