commercetools-8.x-1.2-alpha1/modules/commercetools_decoupled/js/organisms/CheckoutForm.js

modules/commercetools_decoupled/js/organisms/CheckoutForm.js
class CheckoutForm extends HTMLElement {
  async connectedCallback() {
    this.cart = {};
    this.isLoading = true;
    this.customerAddressData = null;
    this.predefinedAddresses = [];
    this.isAnonymous = CheckoutForm.isAnonymous();
    this.renderForm();

    this.cart = await window.commercetools.getCart();
    if (this.cart?.shippingAddress?.id === this.cart?.billingAddress?.id) {
      delete this.cart.billingAddress;
    }

    this.customerAddressData = this.isAnonymous
      ? null
      : await window.commercetools.getUserAddressData();
    this.predefinedAddresses.shippingAddress = this.predefineAddress(
      this.cart,
      this.customerAddressData,
      'shippingAddress',
    );
    this.predefinedAddresses.billingAddress = this.predefineAddress(
      this.cart,
      this.customerAddressData,
      'billingAddress',
    );
    this.isLoading = false;
    this.renderForm();
  }

  predefineAddress(cart, customerAddressData, addressType) {
    if (cart[addressType] != null) {
      return cart[addressType];
    }
    if (this.isAnonymous) {
      return null;
    }

    const defaultIdKey = CheckoutForm.DEFAULT_ADDRESS_KEY_MAP?.[addressType];
    const defaultAddressId = customerAddressData[defaultIdKey];
    const listKey = CheckoutForm.ADDRESS_KEY_MULTIPLE[addressType];
    const list = customerAddressData[listKey] || [];

    return list.find((addr) => addr.id === defaultAddressId) || null;
  }

  renderForm() {
    this.innerHTML = `
      <form data-addresses='${this.prepareAddressForAttr()}' class="commercetools-content-order-submission ${this.isLoading ? 'placeholderify' : ''}" data-drupal-selector="commercetools-order-submission" action="/checkout" method="post" id="commercetools-order-submission" accept-charset="UTF-8">
        <fieldset class="shadow p-3 bg-primary bg-opacity-10 form-item form-wrapper shipping-address">
          <div class="fieldset-wrapper">
            <h2 class="pb-3 mb-4 border-bottom">${Drupal.t('Shipping address')}</h2>
            <div class="shippingAddress-fields"></div>
          </div>
        </fieldset>

        <div class="mt-4 mb-4 shadow p-3 bg-primary bg-opacity-10">
          <div class="form-item form-type-checkbox form-item-sameAddress">
            <input type="checkbox" id="edit-sameAddress" name="sameAddress" value="1" ${this.cart?.billingAddress ? '' : `checked="checked"`} class="form-checkbox form-check-input">
            <label for="edit-sameAddress">${Drupal.t('Billing address same as shipping address')}</label>
          </div>
        </div>

        <fieldset class="shadow p-3 bg-primary bg-opacity-10 form-item form-wrapper billing-address">
          <div class="fieldset-wrapper">
            <h2 class="pb-3 mb-4 border-bottom">${Drupal.t('Billing address')}</h2>
            <div class="billingAddress-fields"></div>
          </div>
        </fieldset>

        <div data-drupal-selector="edit-actions" class="form-actions js-form-wrapper form-wrapper" id="edit-actions">
            <input type="submit" id="edit-submit" name="op" value="${Drupal.t('Save &amp; Continue')}" class="button form-submit btn btn-primary">
        </div>
      </form>
    `;

    ['shippingAddress', 'billingAddress'].forEach((addressType) => {
      const fields = CheckoutForm.addressFields();
      const fieldsWr = this.querySelector(`.${addressType}-fields`);
      fields.forEach((fieldProperties) => {
        const fieldHTML = this.createElement(addressType, fieldProperties);
        fieldsWr.append(fieldHTML);
      });

      if (!this.isAnonymous) {
        this.addAdditionalFields(fieldsWr, addressType);
        // this.addUserAddresses(fieldsWr, addressType);
      }
    });

    // Display/hide billing address section according to the "Same address" checkbox.
    const billingAddress = this.querySelector('.billing-address');
    const sameCheckbox = this.querySelector('input[name="sameAddress"]');
    CheckoutForm.displayInputs(billingAddress, sameCheckbox?.checked || false);
    this.querySelector('[name="sameAddress"]').addEventListener('change', (e) =>
      CheckoutForm.displayInputs(billingAddress, e.target.checked),
    );

    this.querySelector('form').addEventListener(
      'submit',
      CheckoutForm.submitFormEvent,
    );
  }

  addAdditionalFields(fieldsWr, addressType) {
    const additionalFields = CheckoutForm.additionalFields();
    const additionalFieldsElements = {};

    additionalFields.forEach((fieldProperties) => {
      additionalFieldsElements[fieldProperties.name] = this.createElement(
        addressType,
        fieldProperties,
      );
      fieldsWr.append(additionalFieldsElements[fieldProperties.name]);
    });

    CheckoutForm.displayInputs(additionalFieldsElements.title, true);
    CheckoutForm.displayInputs(additionalFieldsElements.setDefault, true);

    additionalFieldsElements.save.addEventListener('change', (e) => {
      CheckoutForm.displayInputs(
        additionalFieldsElements.title,
        !e.target.checked,
      );
      CheckoutForm.displayInputs(
        additionalFieldsElements.setDefault,
        !e.target.checked,
      );
    });

    const createOptionElement = (props = {}) => {
      const optionElement = document.createElement('option');
      Object.assign(optionElement, props);
      return optionElement;
    };
    const addressKeyMultiple = CheckoutForm.ADDRESS_KEY_MULTIPLE[addressType];
    const addresses = this.customerAddressData?.[addressKeyMultiple];
    if (addresses != null) {
      const defaultAddressId =
        this.predefinedAddresses?.[addressType]?.id || 'new';
      const select = document.createElement('select');
      select.textContent = Drupal.t('Fill a new address or choose from saved');
      select.dataset.addressType = addressType;
      select.dataset.previousValue = defaultAddressId;
      select.classList.add('form-select');
      select.name = `${addressType}.id`;
      select.id = `${addressType}.id`;

      const selectLabel = document.createElement('label');
      selectLabel.htmlFor = select.id;
      selectLabel.textContent = Drupal.t(
        'Fill a new address or choose from saved',
      );

      const selectContainer = document.createElement('div');
      selectContainer.append(selectLabel, select);

      const newOption = createOptionElement({
        value: 'new',
        textContent: Drupal.t('Add a new address'),
      });
      select.appendChild(newOption);

      this.customerAddressData?.[addressKeyMultiple].forEach((address) => {
        const option = createOptionElement({
          value: address.id,
          textContent: address.title || address.id,
          selected: address.id === defaultAddressId,
        });
        select.appendChild(option);
      });

      select.addEventListener('change', (e) => {
        CheckoutForm.disableInputs(fieldsWr, e.target.value !== 'new');
        CheckoutForm.displayInputs(
          additionalFieldsElements.save,
          e.target.value !== 'new',
        );
        CheckoutForm.selectAction(
          e.target.dataset.addressType,
          e.target.value,
          e.target.dataset.previousValue,
        );
        e.target.dataset.previousValue = e.target.value;
      });

      fieldsWr.parentNode.insertBefore(selectContainer, fieldsWr);
      CheckoutForm.disableInputs(fieldsWr, defaultAddressId !== 'new');
      CheckoutForm.displayInputs(
        additionalFieldsElements.save,
        defaultAddressId !== 'new',
      );
    }
  }

  createElement(addressType, fieldProperties) {
    fieldProperties.defaultValue =
      this.predefinedAddresses?.[addressType]?.[fieldProperties.name] ||
      fieldProperties.defaultValue ||
      '';

    const fieldHTML = document.createElement(
      `ct-input-${fieldProperties.type}`,
    );
    fieldHTML.properties = {
      ...fieldProperties,
      name: `${addressType}.${fieldProperties.name}`,
    };

    return fieldHTML;
  }

  static displayInputs(element, hide) {
    if (hide === true) {
      element.classList.add('d-none');
      element
        .querySelectorAll('.required')
        .forEach((el) => el.removeAttribute('required'));
    } else {
      element.classList.remove('d-none');
      element
        .querySelectorAll('.required')
        .forEach((el) => el.setAttribute('required', 'required'));
    }
  }

  static disableInputs(element, disable) {
    if (disable === true) {
      element
        .querySelectorAll('.form-item input')
        .forEach((el) => el.setAttribute('disabled', 'disabled'));
      element
        .querySelectorAll('.required')
        .forEach((el) => el.removeAttribute('required'));
    } else {
      element
        .querySelectorAll('.form-item input:not(.disabled)')
        .forEach((el) => el.removeAttribute('disabled'));
      element
        .querySelectorAll('.required')
        .forEach((el) => el.setAttribute('required', 'required'));
    }
  }

  static selectAction(
    addressType,
    selectedAddressId,
    previousSelectedAddressId,
  ) {
    const addressTypeMultiple = CheckoutForm.ADDRESS_KEY_MULTIPLE[addressType];
    const form = document.getElementById('commercetools-order-submission');
    const addresses = CheckoutForm.getAddresses();
    const selectedAddress = addresses[addressTypeMultiple][selectedAddressId];

    if (previousSelectedAddressId === 'new') {
      form
        .querySelectorAll(`.${addressType}-fields input`)
        .forEach((element) => {
          const parts = element.name.split('.');
          const fieldName = parts[1] || element.name;
          addresses[addressTypeMultiple].new[fieldName] =
            element.type === 'checkbox' ? element.checked : element.value;
        });
      this.storeAddresses(addresses);
    }
    CheckoutForm.disableInputs(
      form.querySelector(`.${addressType}-fields`),
      selectedAddressId !== 'new',
    );
    CheckoutForm.setAddressValues(form, addressType, selectedAddress);
  }

  static setAddressValues(form, addressType, selectedAddressData) {
    Object.keys(selectedAddressData).forEach((fieldName) => {
      const input = form.querySelector(
        `.${addressType}-fields [name="${addressType}.${fieldName}"]`,
      );

      if (!input) return;

      if (input.type === 'checkbox' || input.type === 'radio') {
        input.checked = Boolean(selectedAddressData[fieldName]);
      } else {
        input.value = selectedAddressData[fieldName] ?? '';
      }
    });
  }

  static async submitFormEvent(e) {
    e.preventDefault();

    e.target.classList.add('placeholderify');

    // FormData doesn’t include disabled fields.
    e.target
      .querySelectorAll('input[disabled="disabled"]')
      .forEach((el) => el.removeAttribute('disabled'));

    // Get form values.
    const formData = new FormData(e.target);
    const values = {};
    formData.forEach((v, k) => {
      if (k.includes('.')) {
        const [prefix, fieldName] = k.split('.');
        if (typeof values[prefix] === 'undefined') {
          values[prefix] = {};
        }
        values[prefix][fieldName] = v;
      } else {
        values[k] = v;
      }
    });

    const { sameAddress } = values;
    let { shippingAddress, billingAddress } = values;

    if (!CheckoutForm.isAnonymous()) {
      const saveAddress = async function (address, type) {
        return window.commercetools.addAddressCustomer({
          addressData: CheckoutForm.filterAddressValues(address),
          type,
          setDefault: address.setDefault === 'on',
        });
      };

      if (shippingAddress.id === 'new' && shippingAddress.save === 'on') {
        shippingAddress = await saveAddress(shippingAddress, 'shippingAddress');
      }

      if (
        !sameAddress &&
        billingAddress.id === 'new' &&
        billingAddress.save === 'on'
      ) {
        billingAddress = await saveAddress(billingAddress, 'billingAddress');
      }
    }

    // Add shipping and billing addresses necessary for creating order.
    const actions = [
      {
        setShippingAddress: {
          address: CheckoutForm.filterAddressValues(shippingAddress),
        },
      },
      {
        setBillingAddress: {
          address: sameAddress
            ? CheckoutForm.filterAddressValues(shippingAddress)
            : CheckoutForm.filterAddressValues(billingAddress),
        },
      },
    ];
    const cart = await window.commercetools.updateCart({
      id: '[current_cart_or_create:id]',
      version: '[current_cart_or_create:version]',
      actions,
    });

    // Create order by using cart.
    const order = await window.commercetools.createOrderFromCart({
      id: cart.id || '[current_cart:id]',
      version: cart.version || '[current_cart:version]',
    });

    // Remove cart cookie and redirect to the order page.
    document.cookie = `${drupalSettings.commercetoolsDecoupled.sessionCookieName}=; Max-Age=-1;`;
    const orderPath = `${drupalSettings.commercetoolsDecoupled.orderPathPrefix}/${order.id}`;
    window.location.href = orderPath;
  }

  static isAnonymous() {
    return drupalSettings.user.uid === 0;
  }

  static addressFields() {
    return [
      {
        label: Drupal.t('First name'),
        type: 'textfield',
        name: 'firstName',
        required: true,
        disabled: false,
      },
      {
        label: Drupal.t('Last name'),
        type: 'textfield',
        name: 'lastName',
        required: true,
        disabled: false,
      },
      {
        label: Drupal.t('Email'),
        type: 'email',
        name: 'email',
        required: true,
      },
      {
        label: Drupal.t('Country'),
        type: 'textfield',
        name: 'country',
        required: true,
        disabled: true,
        defaultValue: window.drupalSettings.commercetoolsDecoupled.priceCountry,
      },
      {
        label: Drupal.t('Street Address'),
        type: 'textfield',
        name: 'streetName',
        required: true,
        disabled: false,
      },
      {
        label: Drupal.t('Street number'),
        type: 'textfield',
        name: 'streetNumber',
        required: false,
        disabled: false,
      },
      {
        label: Drupal.t('State'),
        type: 'textfield',
        name: 'state',
        required: true,
        disabled: false,
      },
      {
        label: Drupal.t('City'),
        type: 'textfield',
        name: 'city',
        required: true,
        disabled: false,
      },
      {
        label: Drupal.t('ZIP code'),
        type: 'textfield',
        name: 'postalCode',
        required: true,
        disabled: false,
      },
      {
        label: Drupal.t('Phone number'),
        type: 'textfield',
        name: 'phone',
        required: true,
        disabled: false,
      },
    ];
  }

  static additionalFields() {
    return [
      {
        label: Drupal.t('Save this new address to reuse in the next orders'),
        type: 'checkbox',
        name: 'save',
        checked: false,
        required: false,
        disabled: false,
      },
      {
        label: Drupal.t(
          'Fill the title to name this address in the list of saved addresses',
        ),
        type: 'textfield',
        name: 'title',
        required: false,
        disabled: false,
      },
      {
        label: Drupal.t('Use this address as default for the next orders'),
        type: 'checkbox',
        name: 'setDefault',
        checked: false,
        required: false,
        disabled: false,
      },
    ];
  }

  static filterAddressValues(addressData) {
    const addressesFields = window.commercetools.addAddressNodeFields();
    if (addressData.id === 'new') {
      delete addressData.id;
    }
    return Object.fromEntries(
      Object.entries(addressData).filter(([key]) => addressesFields[key]),
    );
  }

  prepareAddressForAttr() {
    const addressData = this.customerAddressData;
    if (addressData === null) {
      return '';
    }

    const data = {
      shippingAddresses: Object.fromEntries(
        addressData.shippingAddresses.map((addr) => [addr.id, addr]),
      ),
      billingAddresses: Object.fromEntries(
        addressData.billingAddresses.map((addr) => [addr.id, addr]),
      ),
    };

    const emptyNewAddress = Object.fromEntries(
      Object.keys(window.commercetools.addAddressNodeFields()).map((key) => [
        key,
        '',
      ]),
    );
    emptyNewAddress.country =
      window.drupalSettings.commercetoolsDecoupled.priceCountry;
    data.shippingAddresses.new = emptyNewAddress;
    data.billingAddresses.new = emptyNewAddress;

    return JSON.stringify(data);
  }

  static getAddress(addressType, id) {
    return CheckoutForm.getAddresses()?.[addressType]?.[id];
  }

  static getAddresses() {
    const form = document.getElementById('commercetools-order-submission');
    const jsonAddressesData = form.getAttribute('data-addresses');
    return jsonAddressesData ? JSON.parse(jsonAddressesData) : null;
  }

  static storeAddresses(addresses) {
    const form = document.getElementById('commercetools-order-submission');
    form.setAttribute('data-addresses', JSON.stringify(addresses));
  }
}

CheckoutForm.ADDRESS_KEY_MULTIPLE = {
  shippingAddress: 'shippingAddresses',
  billingAddress: 'billingAddresses',
};

CheckoutForm.DEFAULT_ADDRESS_KEY_MAP = {
  shippingAddress: 'defaultShippingAddressId',
  billingAddress: 'defaultBillingAddressId',
};

customElements.define('ct-checkout-form', CheckoutForm);

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc