forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			187 lines
		
	
	
	
		
			5.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			187 lines
		
	
	
	
		
			5.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* 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"));
 | 
						|
};
 |