summaryrefslogtreecommitdiffstats
path: root/browser/extensions/formautofill/content/addressFormLayout.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/extensions/formautofill/content/addressFormLayout.mjs')
-rw-r--r--browser/extensions/formautofill/content/addressFormLayout.mjs187
1 files changed, 187 insertions, 0 deletions
diff --git a/browser/extensions/formautofill/content/addressFormLayout.mjs b/browser/extensions/formautofill/content/addressFormLayout.mjs
new file mode 100644
index 0000000000..5e48e6afaa
--- /dev/null
+++ b/browser/extensions/formautofill/content/addressFormLayout.mjs
@@ -0,0 +1,187 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ FormAutofill: "resource://autofill/FormAutofill.sys.mjs",
+ FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
+});
+
+// Defines template descriptors for generating elements in convertLayoutToUI.
+const fieldTemplates = {
+ commonAttributes(item) {
+ return {
+ id: item.fieldId,
+ name: item.fieldId,
+ required: item.required,
+ value: item.value ?? "",
+ };
+ },
+ input(item) {
+ return {
+ tag: "input",
+ type: item.type ?? "text",
+ ...this.commonAttributes(item),
+ };
+ },
+ textarea(item) {
+ return {
+ tag: "textarea",
+ ...this.commonAttributes(item),
+ };
+ },
+ select(item) {
+ return {
+ tag: "select",
+ children: item.options.map(({ value, text }) => ({
+ tag: "option",
+ selected: value === item.value,
+ value,
+ text,
+ })),
+ ...this.commonAttributes(item),
+ };
+ },
+};
+
+/**
+ * Creates an HTML element with specified attributes and children.
+ *
+ * @param {string} tag - Tag name for the element to create.
+ * @param {object} options - Options object containing attributes and children.
+ * @param {object} options.attributes - Element's Attributes/Props (id, class, etc.)
+ * @param {Array} options.children - Element's children (array of objects with tag and options).
+ * @returns {HTMLElement} The newly created element.
+ */
+const createElement = (tag, { children = [], ...attributes }) => {
+ const element = document.createElement(tag);
+
+ for (let [attributeName, attributeValue] of Object.entries(attributes)) {
+ if (attributeName in element) {
+ element[attributeName] = attributeValue;
+ } else {
+ element.setAttribute(attributeName, attributeValue);
+ }
+ }
+
+ for (let { tag: childTag, ...childRest } of children) {
+ element.appendChild(createElement(childTag, childRest));
+ }
+
+ return element;
+};
+
+/**
+ * Generator that creates UI elements from `fields` object, using localization from `l10nStrings`.
+ *
+ * @param {Array} fields - Array of objects as returned from `FormAutofillUtils.getFormLayout`.
+ * @param {object} l10nStrings - Key-value pairs for field label localization.
+ * @yields {HTMLElement} - A localized label element with constructed from a field.
+ */
+function* convertLayoutToUI(fields, l10nStrings) {
+ for (const item of fields) {
+ // eslint-disable-next-line no-nested-ternary
+ const fieldTag = item.options
+ ? "select"
+ : item.multiline
+ ? "textarea"
+ : "input";
+
+ const fieldUI = {
+ label: {
+ id: `${item.fieldId}-container`,
+ class: `container ${item.newLine ? "new-line" : ""}`,
+ },
+ field: fieldTemplates[fieldTag](item),
+ span: {
+ class: "label-text",
+ textContent: l10nStrings[item.l10nId] ?? "",
+ },
+ };
+
+ const label = createElement("label", fieldUI.label);
+ const { tag, ...rest } = fieldUI.field;
+ const field = createElement(tag, rest);
+ label.appendChild(field);
+ const span = createElement("span", fieldUI.span);
+ label.appendChild(span);
+
+ yield label;
+ }
+}
+
+/**
+ * Retrieves the current form data from the current form element on the page.
+ *
+ * @returns {object} An object containing key-value pairs of form data.
+ */
+export const getCurrentFormData = () => {
+ const formElement = document.querySelector("form");
+ const formData = new FormData(formElement);
+ return Object.fromEntries(formData.entries());
+};
+
+/**
+ * Checks if the form can be submitted based on the number of non-empty values.
+ * TODO(Bug 1891734): Add address validation. Right now we don't do any validation. (2 fields mimics the old behaviour ).
+ *
+ * @returns {boolean} True if the form can be submitted
+ */
+export const canSubmitForm = () => {
+ const formData = getCurrentFormData();
+ const validValues = Object.values(formData).filter(Boolean);
+ return validValues.length >= 2;
+};
+
+/**
+ * Generates a form layout based on record data and localization strings.
+ *
+ * @param {HTMLFormElement} formElement - Target form element.
+ * @param {object} record - Address record, includes at least country code defaulted to FormAutofill.DEFAULT_REGION.
+ * @param {object} l10nStrings - Localization strings map.
+ */
+export const createFormLayoutFromRecord = (
+ formElement,
+ record = { country: lazy.FormAutofill.DEFAULT_REGION },
+ l10nStrings = {}
+) => {
+ // Always clear select values because they are not persisted between countries.
+ // For example from US with state NY, we don't want the address-level1 to be NY
+ // when changing to another country that doesn't have state options
+ const selects = formElement.querySelectorAll("select:not(#country)");
+ for (const select of selects) {
+ select.value = "";
+ }
+
+ // Get old data to persist before clearing form
+ const formData = getCurrentFormData();
+ record = {
+ ...record,
+ ...formData,
+ };
+
+ formElement.innerHTML = "";
+ const fields = lazy.FormAutofillUtils.getFormLayout(record);
+
+ const layoutGenerator = convertLayoutToUI(fields, l10nStrings);
+
+ for (const fieldElement of layoutGenerator) {
+ formElement.appendChild(fieldElement);
+ }
+
+ document.querySelector("#country").addEventListener(
+ "change",
+ ev =>
+ // Allow some time for the user to type
+ // before we set the new country and re-render
+ setTimeout(() => {
+ record.country = ev.target.value;
+ createFormLayoutFromRecord(formElement, record, l10nStrings);
+ }, 300),
+ { once: true }
+ );
+
+ // Used to notify tests that the form has been updated and is ready
+ window.dispatchEvent(new CustomEvent("FormReadyForTests"));
+};