import "@supersoniks/concorde/core/components/ui/button/button"; import { ConcordeWindow, HTMLFormControl, } from "@supersoniks/concorde/core/_types/types"; import { Loader } from "@supersoniks/concorde/core/components/ui/loader/loader"; import Subscriber from "@supersoniks/concorde/core/mixins/Subscriber"; import API, { ResultTypeInterface } from "@supersoniks/concorde/core/utils/api"; import Objects from "@supersoniks/concorde/core/utils/Objects"; import { PublisherManager } from "@supersoniks/concorde/core/utils/PublisherProxy"; import { Message } from "@supersoniks/concorde/core/_types/types"; import { HTML } from "@supersoniks/concorde/utils"; import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators.js"; type SubmitResult = | ({ messages: Message[] } & ResultTypeInterface) | null | undefined; const tagName = "sonic-submit"; declare const window: ConcordeWindow; /** * ###L'élément submit permet d'envoyer des données en rest * * Les données envoyées sont celles présentes dans le publisher dont l'adresse est renseignée via l'attribut *formDataProvider*. * Cet attribut est également utilisé par les éléments de form comme *sonic-input*, ou *sonic-select*, qui remplissent ce publisher avec leur attribut *value* en fonction de leur attribut *name* * * L'api est configuré à la manière d'un fetcher. * * L'attribut *method* permet de choisir la méthode d'envoie : *put/delete/post*, *post* étant la méthode par défaut. * * Si l'attribut *onClick* est présent, les données sont envoyées quand on click dans son contenu * * Si l'attribut *onEnterKey* est présent, les données sont envoyées quand on appuie sur la touche entrée d'un élément contenu dans le *sonic-submit* ayant le focus * * Pendant l'envoi, les éléments *sonic-submit* ayant le même attribut *dataProvider* ont la propriété *disabled="disabled"* ce qui a pour effet de désactiver leur contenu * * L'attribut *clearedDataOnSuccess* peut être utilisé pour vider les données des dataProvider correspondants lorsque l'appel à l'api a fourni un résultat. */ @customElement(tagName) export class Submit extends Subscriber(LitElement) { static styles = css` [data-disabled] { opacity: 0.3; pointer-events: none; user-select: none; } `; @property({ type: String }) submitResultKey: string | null = null; @property({ type: Boolean }) disabled = false; @property({ type: String }) endPoint: string | null = null; @property() name = ""; @property() value = ""; api: API | null = null; connectedCallback() { if (this.hasAttribute("onClick")) { this.addEventListener("click", this.submit); } if (this.hasAttribute("onEnterKey")) { this.addEventListener("keydown", this.submit); } super.connectedCallback(); this.api = new API(this.getApiConfiguration()); } submitNativeForm() { const form: HTMLFormElement = HTML.getClosestForm(this) as HTMLFormElement; if (!form) return; const formDataProvider = this.getAncestorAttributeValue("formDataProvider"); const dataToSend = { ...PublisherManager.get(formDataProvider).get() }; delete dataToSend.needsCaptchaValidation; for (const name in dataToSend) { if (name == "isFormValid") continue; let control = form.querySelector( 'input[name="' + name + '"], select[name="' + name + '"], textarea[name="' + name + '"]' ) as HTMLFormControl; if (!control) { control = document.createElement("input"); control.type = "hidden"; control.name = name; form.appendChild(control); } let value = dataToSend[name]; if (Array.isArray(value)) value = value.join(","); if (control.type === "checkbox" || control.type === "radio") { if (value) (control as HTMLInputElement).checked = true; } else { control.value = value; } } const submitButton = document.createElement("input") as HTMLInputElement; submitButton.name = this.name; submitButton.style.display = "none"; submitButton.value = this.value; submitButton.type = "submit"; form.appendChild(submitButton); if (submitButton) submitButton.click(); } clickTimeStamp = 0; submit = async (e?: Event) => { if (this.disabled) return; if (e instanceof KeyboardEvent && !(e.key === "Enter")) return; if (e instanceof MouseEvent) { e.stopPropagation(); } const formPublisher = PublisherManager.getInstance().get( this.getAncestorAttributeValue("formDataProvider") ); // // Validation du formulaire formPublisher.isFormValid.set(true); formPublisher.invalidateForm(); if (!formPublisher.isFormValid.get()) return; this.disabled = true; Loader.show(); formPublisher.isFormValid; // support native html form const native = this.hasAttribute("native"); if (native) { this.submitNativeForm(); return; } // //Recup données const method = this.getAttribute("method")?.toLocaleLowerCase() || "post"; const sendAsFormData = this.hasAttribute("sendAsFormData"); const formData = formPublisher.get(); delete formData.isFormValid; const headesDataProvider = this.getAncestorAttributeValue( "headersDataProvider" ); const headerPublisher = headesDataProvider ? PublisherManager.getInstance().get(headesDataProvider) : null; let headerData: any = {}; if (headerPublisher) headerData = headerPublisher.get(); let result: SubmitResult = null; const dataProvider = this.getAncestorAttributeValue("dataProvider"); const endPoint = this.endPoint || dataProvider; const sendData = async () => { const dataToSend = { ...formData }; delete dataToSend.needsCaptchaValidation; delete headerData.needsCaptchaValidation; // //envoi données if (sendAsFormData) { result = await this.api?.submitFormData( endPoint, dataToSend, method, headerData ); } else { switch (method) { case "put": result = await this.api?.put(endPoint, dataToSend, headerData); break; case "patch": result = await this.api?.patch(endPoint, dataToSend, headerData); break; case "delete": result = await this.api?.delete(endPoint, dataToSend, headerData); break; case "get": // add dataToSend to endpoint const params = new URLSearchParams(); if (dataToSend) { for (const key in dataToSend) { params.append(key, dataToSend[key]); } } const paramString = "?" + params.toString(); result = await this.api?.get(endPoint + paramString, headerData); break; default: result = await this.api?.post(endPoint, dataToSend, headerData); break; } } Loader.hide(); if (!result) result = { messages: [{ content: "Network Error", status: "error" }], }; // Si result ne contient que la réponse HTTP, avec un status not ok, on ajoute un message else if ( result._sonic_http_response_ && !result._sonic_http_response_.ok && Object.keys(result).length === 1 ) result.messages = [{ content: "Network Error", status: "error" }]; const clearedDataProvider = this.getAncestorAttributeValue( "clearedDataOnSuccess" ); if (clearedDataProvider) { clearedDataProvider .split(" ") .forEach((dataProvider) => PublisherManager.get(dataProvider).set({}) ); } const username_key = this.hasAttribute("usernameKey") ? this.getAttribute("usernameKey") : "username"; const password_key = this.hasAttribute("passwordKey") ? this.getAttribute("passwordKey") : "password"; if ( this.api?.lastResult?.ok && dataToSend[username_key] && dataToSend[password_key] ) { this.saveCredentials( dataToSend[username_key], dataToSend[password_key] ); } if (this.submitResultKey) { result = Objects.traverse( result, this.submitResultKey.split("."), true ); } const submitResultDataProvider = this.getAncestorAttributeValue( "submitResultDataProvider" ); if (submitResultDataProvider) PublisherManager.get(submitResultDataProvider).set(result); this.disabled = false; //dispacher event this.dispatchEvent( new CustomEvent("submit", { detail: result, bubbles: true, composed: true, }) ); }; const captchaPublisher = // TODO Comprendre pourquoi header publisher headerPublisher?.needsCaptchaValidation.get() ? headerPublisher : formPublisher.needsCaptchaValidation.get() ? formPublisher : null; if (captchaPublisher) { captchaPublisher.captchaToken.set("request_token"); const captchaAssign = (token?: string) => { if (token != "request_token") { sendData(); // Après l'envoie des données, on supprime ce onAssign. // Sinon les handler se cumuleraient après plusieurs submit consécutifs. captchaPublisher.captchaToken.offAssign(captchaAssign); } else { Loader.hide(); this.disabled = false; } }; captchaPublisher.captchaToken.onAssign(captchaAssign); } else { sendData(); } }; async saveCredentials(username: string, password: string) { // Check if the browser supports password credentials (and the Credential Management API) if ("PasswordCredential" in window) { const credential = new window.PasswordCredential({ id: username, // name: "Carina Anand", // In case of a login, the name comes from the server. password: password, }); await navigator.credentials.store(credential); } } protected render(): unknown { return html`