marketo_suite-1.0.x-dev/js/e3_marketo_forms.js

js/e3_marketo_forms.js
/**
 * @file
 * Handles Marketo form injections and general functionality.
 */
(function (Drupal, cookies) {
  'use strict';

  /**
   * Global object to hold some global Marketo Forms data.
   *
   * @type {Drupal~marketoForms}
   */
  Drupal.marketoForms = Drupal.marketoForms || {};

  /**
   * Array of already loaded forms to prevent third-party Marketo scripts form
   * creating duplicates.
   *
   * @type {Array}
   */
  Drupal.marketoForms.loadedForms = Drupal.marketoForms.loadedForms || [];

  /**
   * Marketo forms base logic.
   *
   * @type {Drupal~behavior}
   *
   * @prop {Drupal~behaviorAttach} attach
   *   Inject all Marketo forms and initialize submission behaviors.
   */
  Drupal.behaviors.marketoForms = {
    attach: function (context, settings) {

      // Marketo tracking cookie.
      const marketoCookie = cookies.get('_mkto_trk');

      /**
       * Embed Marketo form.
       *
       * @param {Array} marketoConfig
       *   Marketo form configuration array.
       */
      const init = function (marketoConfig) {
        // Integrate with the GDPR module if it is installed.
        let cookiesAccepted = true;
        if (typeof GDPR !== 'undefined') {
          cookiesAccepted = GDPR.cookiesAccepted();
        }

        if (cookiesAccepted  && typeof Munchkin !== 'undefined') {

          // Make sure Marketo cookie is added if Munchkin is set.
          if (marketoCookie === undefined) {
            Munchkin.init(marketoConfig.munchkinId);
          }
        }
        else if (!cookiesAccepted) {
          cookies.remove('_mkto_trk');
        }

        // Initialize and chain-inject Marketo Forms.
        const formInstances = context.querySelectorAll("form." + marketoConfig.htmlClass);
        if (formInstances.length) {
          injectMarketoForms(marketoConfig);
        }

        // Operations to perform when form is ready after being injected.
        MktoForms2.whenReady(function (form) {
          const formEl = context.querySelector('.' + marketoConfig.htmlClass);
          if (formEl.length && !formEl?.classList.contains('processed-marketo')) {

            // Add field name class for form row.
            form.getFormElem().find('.mktoField').each(function (e) {
              const formField = this;
              const fieldType = formField.getAttribute('type');
              const elementName = formField.getAttribute('name');

              if (typeof elementName !== 'undefined') {
                formField.closest('.mktoFormRow')?.classList.add('mktoField' + elementName);
              }

              // Track the value state of text inputs.
              if (fieldType !== 'checkbox' && fieldType !== 'radio') {
                setInputStateTracker(formField);
              }

              // Track the value state of select elements.
              if (formField.tagName.toLowerCase() === 'select') {
                formField.closest('.mktoFieldWrap')?.classList.add('marketo-form-item');
                setSelectStateTracker(formField);
              }

              // Track the value state of text areas.
              if (formField.tagName.toLowerCase() === 'textarea') {
                formField.closest('.mktoFieldWrap')?.classList.add('marketo-form-item');
                setInputStateTracker(formField);
              }

              // Track checkboxes.
              if (fieldType === 'checkbox') {
                formField.closest('.mktoFieldWrap')?.classList.add('marketo-checkbox');
              }

              // Track radios.
              if (fieldType === 'radio') {
                formField.closest('.mktoFieldWrap')?.classList.add('marketo-radio');
              }
            });
          }

          // Setting a unique event for use in external scripts
          const elementRenderEventName = 'whenFormElRendered' + formEl.getAttribute('data-form-id');
          const elementRenderEvent = new Event(elementRenderEventName);
          formEl.dispatchEvent(elementRenderEvent);

          if (typeof(marketoConfig.removeSourceStyles) !== 'undefined' && marketoConfig.removeSourceStyles) {
            removeMarketoSourceStylesheets(form, marketoConfig);
          }
          else {
            // Reveal the form once it's been processed.
            formEl?.classList.add('processed-marketo');
            formEl.closest('.marketo-steps-wrapper')?.classList.remove('hidden');
          }
        });
      };

      /**
       * Retrieve Marketo Settings.
       *
       * @return {Array}
       *   Array of defined Marketo Settings.
       */
      const getMarketoFormsSettings = function () {
        if (typeof(settings.marketoForms) !== 'undefined' && settings.marketoForms) {

          return settings.marketoForms;
        }

        return [];
      };

      /**
       * Initialise focus and value trackers for theming.
       *
       * @param {object} input
       *   Input object to process.
       */
      const setInputStateTracker = function (input) {
        input.closest('.mktoFieldWrap')?.classList.add('marketo-form-item');
        input.addEventListener('focus', (event) => {
          event.currentTarget.closest('.marketo-form-item')?.classList.add('marketo-focus-form-item');
        });

        input.addEventListener('blur', (event) => {
          event.currentTarget.closest('.marketo-form-item')?.classList.remove('marketo-focus-form-item');
        });

        input.addEventListener('propertychange change paste input', function () {
          const focusedItem = this;
          const textVal = this.val();

          if (textVal === "" || textVal.length < 1) {
            focusedItem.closest('.marketo-form-item').removeClass('has-value');
          } else {
            focusedItem.closest('.marketo-form-item').addClass('has-value');
          }
        });

        if (input.value) {
          input.closest('.marketo-form-item')?.classList.add('has-value');
        }
      };

      /**
       * Initialize focus and state trackers for select theming.
       *
       * @param {object} select
       *   Select object to process.
       */
      const setSelectStateTracker = function (select) {
        select.closest('.mktoFieldWrap')?.classList.add('marketo-form-item', 'marketo-form-item-select');

        select.addEventListener('focus', (event) => {
          event.currentTarget.closest('.marketo-form-item')?.classList.add('marketo-focus-form-item');
        });

        select.addEventListener('blur', (event) => {
          event.currentTarget.closest('.marketo-form-item')?.classList.remove('marketo-focus-form-item');
        });

        select.addEventListener('change', function (event) {
          event.currentTarget.closest('.marketo-form-item')?.classList.add('has-value');
        });

        if (select.value) {
          select.closest('.marketo-form-item')?.classList.add('has-value');
        }
      };

      /**
       * Chain load and inject all Marketo Forms.
       *
       * @param {Array} marketoConfig
       *   Configuration settings the for Form instance.
       */
      const injectMarketoForms = function (marketoConfig) {
        const arrayFrom = Function.prototype.call.bind(Array.prototype.slice),
            marketoFormDataAttr = "data-form-id";

        // Make labels unique for accessibility.
        MktoForms2.whenRendered(function (form) {
          const formEl = form.getFormElem()[0],
              randomSuffix = "_" + new Date().getTime() + Math.random();

          arrayFrom(formEl.querySelectorAll("label[for]")).forEach(function (labelEl) {
            const forEl = formEl.querySelector('[id="' + labelEl.htmlFor + '"]');
            if (forEl) {
              labelEl.htmlFor = forEl.id = forEl.id + randomSuffix;
            }
          });
        });

        const loadForm = MktoForms2.loadForm.bind(MktoForms2, marketoConfig.instanceHost, marketoConfig.munchkinId, marketoConfig.formId),
            formEls = arrayFrom(document.querySelectorAll('form[' + marketoFormDataAttr + '="' + marketoConfig.formId + '"]:not(.mktoHasWidth)'));

        let dataInstances = [];
        formEls.forEach(function (element, index) {
          const dataInstance = element.getAttribute('data-instance');
          if (dataInstance && dataInstances.indexOf(dataInstance) > -1) {
            formEls.splice(index, 1);
          }
          else {
            dataInstances.push(dataInstance);
          }
        });

        // Chain load forms. This will ensure the same form can be loaded on a
        // page multiple times.
        (function loadFormCb(formEls) {
          // Retrieve the form
          const formEl = formEls.shift();

          if (typeof(formEl) !== 'undefined') {
            const dataInstance = formEl.getAttribute('data-instance');
            formEl.id = "mktoForm_" + marketoConfig.formId;

            // Only load the form if the form instance hasn't been loaded yet.
            // Also make sure that there's no form with the ID we're about to add
            // in the DOM already.
            const formInstance = context.querySelector('form#marketo-form-' + marketoConfig.formId + "-" + dataInstance);
            if ((!formInstance || formInstance.length < 1) &&
                Drupal.marketoForms.loadedForms.indexOf(marketoConfig.formId + "-" + dataInstance) === -1) {

              // Load the form.
              loadForm(function (form) {
                // Save loaded form for future reference.
                Drupal.marketoForms.loadedForms.push(marketoConfig.formId + "-" + dataInstance);
                formEl.id = 'marketo-form-' + marketoConfig.formId + "-" + dataInstance;

                // Pre-fill the form if enabled.
                if (marketoConfig.enablePrefill) {
                  prefillMarketoForm(form);
                }

                // Execute all post-load stuff for the form.
                marketoFormPostLoad(form, marketoConfig, dataInstance);

                if (formEls.length > 0) {
                  loadFormCb(formEls);
                }
              });
            }
          }
        })(formEls);
      };

      /**
       * Pre-fill Marketo Form.
       *
       * @param {Object} form
       *   Loaded Marketo Form object.
       */
      const prefillMarketoForm = function (form) {
        if (marketoCookie !== undefined) {
          const prefillRequest = new XMLHttpRequest();
          prefillRequest.setRequestHeader('Content-Type', 'application/json');

          prefillRequest.onreadystatechange = function() {
            if (prefillRequest.readyState === 4 && prefillRequest.status === 200) {
              const prefillValues = {};
              const currentValues = form.getValues();
              const data = JSON.parse(prefillRequest.responseText);

              for (let key in data) {
                // Skip loop if the property is from prototype
                if (!data.hasOwnProperty(key)) continue;

                // Skip if the filled has already been pre-filled.
                if (currentValues[key]) continue;

                prefillValues[key] = data[key];
              }

              // Prefill data.
              form.setValues(prefillValues);

              // Mark pre-filled elements as having data. For theming.
              let filledValues = form.vals(),
                  formElem = form.getFormElem();

              formElem.find('input,textarea,select').each(function (el) {
                let elemName = el.getAttribute('name');

                if (
                    typeof(elemName) !== 'undefined'
                    && el.getAttribute('type') !== 'hidden'
                    && typeof(filledValues[elemName]) !== 'undefined'
                    && filledValues[elemName] !== ''
                ) {

                  el.closest('.marketo-form-item')?.classList.add('has-value');
                }
              });
            }
          }

          prefillRequest.open('POST', '/marketo/prefill', true);
          prefillRequest.send(JSON.stringify({
            trkValue: marketoCookie,
            formFields: form.getValues()
          }));
        }
      };

      /**
       * Execute all post-load Marketo operations.
       *
       * @param {Object} form
       *   Marketo Form.
       * @param {Array} marketoConfig
       *   Marketo Form configuration.
       * @param {String} dataInstance
       *   Configuration instance number.
       */
      const marketoFormPostLoad = function(form, marketoConfig, dataInstance) {
        let instanceConfig = marketoConfig[dataInstance];

        // Add/set hidden fields if settings for them were added.
        if (typeof(instanceConfig.hiddenFields) !== 'undefined' && instanceConfig.hiddenFields) {
          form.addHiddenFields(instanceConfig.hiddenFields);
        }

        // Submit button text override by component.
        if (typeof(instanceConfig.overrideSubmitText) !== 'undefined' && instanceConfig.overrideSubmitText) {
          form.getFormElem().find('button.mktoButton').html(instanceConfig.overrideSubmitText);
        }

        // On successful form submission, check for settings to determine
        // next steps.
        form.onSuccess(function (values, followUpUrl) {
          const submitEvent = new CustomEvent('marketoSubmit', { detail: {
              'form': form,
              'values': values,
              'followUpUrl': followUpUrl,
            }});
          window.dispatchEvent(submitEvent);

          // Run all specified submission callbacks.
          if (typeof(instanceConfig.submissionCallbacks) !== 'undefined' && instanceConfig.submissionCallbacks) {
            for (let marketoSubmissionCallback in instanceConfig.submissionCallbacks) {
              const marketoSubmissionCallbackData = instanceConfig.submissionCallbacks[marketoSubmissionCallback];

              if (typeof Drupal.behaviors.marketoForms[marketoSubmissionCallback] === "function") {
                Drupal.behaviors.marketoForms[marketoSubmissionCallback](form, marketoSubmissionCallbackData, values, marketoConfig, dataInstance);
              }
            }
          }

          // Prevent the default redirects that could be set from within
          // Marketo if a form has been set to use a different submission
          // behavior.
          if (typeof(instanceConfig.skipMarketoRedirects) !== 'undefined' && instanceConfig.skipMarketoRedirects) {
            return false;
          }
        });
      };

      /**
       * Remove Marketo-sourced stylesheets.
       *
       * @param {object} form
       *   Marketo form object.
       * @param {Array} marketoConfig
       *   Marketo form config.
       *
       * @see http://developers.marketo.com/javascript-api/forms/api-reference/
       */
      const removeMarketoSourceStylesheets = function (form, marketoConfig) {
        const formElement = context.querySelector("form." + marketoConfig.htmlClass);

        // Remove inline styles that Marketo adds to most elements.
        document.querySelectorAll('*[class^="mkto"][style]').forEach(e => e.removeAttribute('style'));

        // Remove some core Marketo css classes in favor of our own.
        if (formElement) {
          formElement.removeAttribute('style');
          formElement.classList.remove('mktoForm')
          formElement.querySelector('.mktoButton')?.classList.remove('mktoButton');
        }

        // Remove Marketo "required" divs and add Drupal's "form-required" class
        // to form labels instead. Remove mktoClear, mktoOffset, mktoGutter
        // empty div containers.
        context.querySelectorAll('.mktoAsterix, .mktoClear, .mktoOffset, .mktoGutter').forEach(e => e.remove());
        context.querySelectorAll('.mktoRequiredField .mktoLabel').forEach(e => e.classList.add('form-required'));

        // Disable remote stylesheets and local form styles.
        const arrayFrom = Function.prototype.call.bind(Array.prototype.slice);
        const styleSheets = arrayFrom(document.styleSheets);
        styleSheets.forEach(function (ss) {
          if ([mktoForms2BaseStyle, mktoForms2ThemeStyle].indexOf(ss.ownerNode) != -1 || form.getFormElem()[0].contains(ss.ownerNode)) {
            ss.disabled = true;
          }
        });

        // Remove inline marketo custom fonts.
        document.querySelectorAll('#mktoFontUrl').forEach(e => e.remove());

        // Remove the rest of inline styles.
        const mktoFormsHeaderStyle = document.querySelector('#mktoForms2ThemeStyle');
        if (mktoFormsHeaderStyle) {
          const mktoFormsHeaderStyleExtra = mktoFormsHeaderStyle.nextElementSibling;
          if (mktoFormsHeaderStyleExtra) {
            if (mktoFormsHeaderStyleExtra.length > 0 && mktoFormsHeaderStyleExtra.tagName.toLowerCase() === 'style') {
              mktoFormsHeaderStyleExtra.remove();
            }
          }
        }

        if (formElement) {
          formElement.querySelectorAll('style').forEach(e => e.remove());

          // Reveal the form once it's been processed.
          formElement.classList.add('processed-marketo');
          formElement.closest('.marketo-steps-wrapper')?.classList.remove('hidden');
        }
      };

      /**
       * Initiate Marketo through an alternative instance host.
       *
       * This is mostly used to bypass Firefox tracking protection, but will
       * also fire in case original forms js was not successfully loaded.
       */
      const initProxyMarketo = function (marketoConfig) {
        const s = document.createElement('script');
        s.onload = marketoPreInit;
        s.setAttribute('type', 'text/javascript');
        s.setAttribute('src', marketoConfig.instanceHost + '/js/forms2/js/forms2.min.js');
        document.getElementsByTagName('head')[0].appendChild(s);
      };

      /**
       * Run final checks before initializing the Marketo Forms injection.
       *
       * This function is here to provide possible integration with other
       * modules if there's a need to delay Marketo initialization.
       */
      const marketoPreInit = function (marketoConfig) {
        // If we still don't have Marketo Assets loaded at this time, load
        // custom message and prevent further actions.
        const form = context.querySelector("form." + marketoConfig.htmlClass);

        // If the form didn't load - it got blocked by the adblock.
        setTimeout(function () {
          if (form && form.childElementCount < 1 && typeof marketoConfig.loadErrorMessage !== 'undefined') {
            form.closest('.marketo-steps-wrapper')?.classList.remove('hidden');
            form.parentElement.innerHTML = marketoConfig.loadErrorMessage;
          }
        }, 10000);

        // Delay init if GDPR module is installed.
        if (typeof GDPR !== 'undefined' && !GDPR.initialisationComplete) {
          window.addEventListener("gdpr:load", function () {
            init(marketoConfig);
          });
        }
        else {
          init(marketoConfig);
        }
      };

      // Init Marketo embed procedure.
      const marketoSettingsAll = getMarketoFormsSettings();
      for (let marketoProp in marketoSettingsAll) {
        if (!marketoSettingsAll.hasOwnProperty(marketoProp)) {
          continue;
        }

        if (marketoProp === 'munchkinId') {
          continue;
        }

        let marketoConfig = marketoSettingsAll[marketoProp];
        if (typeof MktoForms2 === 'undefined') {
          marketoConfig.instanceHost = marketoConfig.alternativeInstanceHost;
          initProxyMarketo(marketoConfig);
        }
        else {
          marketoPreInit(marketoConfig);
        }
      }

    },

    /**
     * Submission callback to replace the form with a confirmation.
     *
     * @param {object} form
     *   Marketo form object.
     * @param {string} behaviorData
     *   Submission behavior configuration.
     * @param {Array} values
     *   Submitted values.
     * @param {object} marketoConfig
     *   General marketo configuration.
     */
    replaceWithConfirmation: function replaceWithConfirmation(form, behaviorData, values, marketoConfig) {
      // Replace the form with a confirmation if it has been provided.
      if (behaviorData) {
        const formElem = form.getFormElem();
        const marketoWrapper = formElem.closest(marketoConfig.entityWrapper);
        marketoWrapper.replaceWith(behaviorData).addClass('marketo-form-submitted');
      }
    },

    /**
     * Submission callback to redirect to a page after the submission.
     *
     * @param {object} form
     *   Marketo form object.
     * @param {string} behaviorData
     *   Submission behavior configuration.
     * @param {Array} values
     *   Submitted values.
     * @param {object} marketoConfig
     *   General marketo configuration.
     */
    redirectToPage: function redirectToPage(form, behaviorData, values, marketoConfig) {
      // Replace the form with a confirmation if it has been provided.
      if (behaviorData) {
        window.location.href = behaviorData;
      }
    }
  };

})(Drupal, window.Cookies);

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

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