PageSourceSearch

https://www.nailsmag.com/bbmforms/2.4/bbmforms.js

js nailsmag.com collected 2026-10-02 05:36:41 UTC 132,211 bytes, 3,916 lines download raw bytes

1(function () {
2  'use strict';
3
4  function BBMForms(options) {
5    if (!options) {
6      throw new Error('No options passed to BBMForms.');
7    }
8    if (!options.formId) {
9      throw new Error('No formId option passed to BBMForms.');
10    }
11    if (!options.hostName) {
12      options.hostName = window.location.hostname;
13    }
14
15    //override default options with given options
16    this.options = bbmFormsJQ.extend({}, BBMForms.defaultOptions, options);
17
18    //Use default if empty string
19    if (!this.options.origin)
20      this.options.origin = BBMForms.defaultOptions.origin;
21    if (!this.options.title) {
22      var title = document.getElementsByTagName('title');
23      if (title.length > 0) this.options.title = title[0].innerText.trim();
24      else this.options.title = BBMForms.defaultOptions.title; //empty
25    }
26
27    this.options.title = this.options.title.trim(); //remove whitespaces
28
29    this.userId = getCookie(BBMForms.constants.userIdCookieName);
30
31    this.userSsoId = this.options.userSsoId ? this.options.userSsoId : null;
32
33    this.userSsoEmail = this.options.userSsoEmail
34      ? this.options.userSsoEmail
35      : null;
36
37    var enableCapture = BBMForms.defaultOptions.enableMetadataCapture;
38    if (typeof options.enableMetadataCapture != 'undefined')
39      enableCapture = options.enableMetadataCapture;
40    this.options.enableMetadataCapture = enableCapture;
41    if (enableCapture) {
42      var paramList = BBMForms.defaultOptions.metadataParamList;
43      if (typeof options.metadataParamList != 'undefined')
44        paramList = options.metadataParamList;
45
46      var prefixList = BBMForms.defaultOptions.metadataPrefixList;
47      if (typeof options.metadataPrefixList != 'undefined')
48        prefixList = options.metadataPrefixList;
49
50      bbmFormsJQ.each(prefixList, function (i) {
51        var parameters = getUrlParameterByPrefix(prefixList[i]);
52        if (parameters.length > 0) {
53          bbmFormsJQ.each(parameters, function (index, value) {
54            if (bbmFormsJQ.inArray(value, paramList) < 0) {
55              paramList.push(value);
56            }
57          });
58        }
59      });
60
61      this.options.metadataParamList = paramList;
62    }
63  }
64
65  function getOwnPropertyNameInsensitive(obj, value) {
66    for (var prop in obj) {
67      if (prop.toLowerCase() === value.toLowerCase()) {
68        return prop;
69      }
70    }
71    return null;
72  }
73
74  function decodeHtml(str) {
75    var $txt = bbmFormsJQ('<textarea/>');
76    return $txt.html($txt.html(str).text()).text();
77  }
78
79  function setCookie(cname, cvalue, exdays, hostName) {
80    var d = new Date();
81    d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
82    var expires = ';expires=' + d.toUTCString();
83
84    var domain = '';
85    if (hostName) {
86      var hnArr = hostName.split('.');
87      if (hnArr.length > 1) {
88        var hn = hnArr.reverse();
89        domain = ';domain=.' + hn[1] + '.' + hn[0];
90      } else {
91        domain = ';domain=' + hostName;
92      }
93    }
94
95    document.cookie = cname + '=' + cvalue + expires + domain + ';path=/';
96  }
97
98  function getCookie(cname) {
99    var name = cname + '=';
100    var decodedCookie = decodeURIComponent(document.cookie);
101    var ca = decodedCookie.split(';');
102    for (var i = 0; i < ca.length; i++) {
103      var c = ca[i];
104      while (c.charAt(0) == ' ') {
105        c = c.substring(1);
106      }
107      if (c.indexOf(name) == 0) {
108        return c.substring(name.length, c.length);
109      }
110    }
111    return '';
112  }
113
114  function postFormData(
115    formData,
116    callbackPassThrough,
117    callbackDisplayForm,
118    callbackError
119  ) {
120    bbmFormsJQ.ajax({
121      type: 'post',
122      timeout: 300000, //5 minutes to allow for slow connection large file uploads set to 200MB max per request.
123      url: BBMForms.constants.apiPath + '/lead',
124      data: formData,
125      dataFilter: function () {}, //Empty out dataFilter that may exist.
126      processData: false,
127      contentType: false,
128      success: function (data, status, xhr) {
129        if (!data && (xhr.responseJSON || xhr.responseText))
130          data = xhr.responseJSON
131            ? xhr.responseJSON
132            : JSON.parse(xhr.responseText);
133
134        callbackPassThrough(data);
135      },
136      error: function (xhr, status, error) {
137        if (xhr.status === 200 && (xhr.responseJSON || xhr.responseText)) {
138          //When responseJSON is undefined and fails to parse it comes here. Handle as passthrough.
139          var data = xhr.responseJSON
140            ? xhr.responseJSON
141            : JSON.parse(xhr.responseText);
142          callbackPassThrough(data);
143        } else if (
144          (xhr.status === 400 || xhr.status === 403) &&
145          (xhr.responseJSON || xhr.responseText)
146        ) {
147          //Cannot pass through for having either incomplete or invalid fields.
148          var data = xhr.responseJSON
149            ? xhr.responseJSON
150            : JSON.parse(xhr.responseText);
151          data.stepToDisplay = parseInt(data.stepToDisplay);
152          callbackDisplayForm(data);
153        } else {
154          console.log('Failed to post data. ' + error);
155          callbackError('Error: Failed to post form data.');
156        }
157      },
158    });
159  }
160
161  function postSerializedForm(
162    formData,
163    callbackPassThrough,
164    callbackDisplayForm,
165    callbackError
166  ) {
167    bbmFormsJQ.ajax({
168      type: 'post',
169      url: BBMForms.constants.apiPath + '/lead',
170      data: formData,
171      dataFilter: function () {}, //Empty out dataFilter that may exist.
172      success: function (data, status, xhr) {
173        if (!data && (xhr.responseJSON || xhr.responseText))
174          data = xhr.responseJSON
175            ? xhr.responseJSON
176            : JSON.parse(xhr.responseText);
177
178        callbackPassThrough(data);
179      },
180      error: function (xhr, status, error) {
181        if (xhr.status === 200 && (xhr.responseJSON || xhr.responseText)) {
182          //When responseJSON is undefined and fails to parse it comes here. Handle as passthrough.
183          var data = xhr.responseJSON
184            ? xhr.responseJSON
185            : JSON.parse(xhr.responseText);
186          callbackPassThrough(data);
187        } else if (
188          (xhr.status === 400 || xhr.status === 403) &&
189          (xhr.responseJSON || xhr.responseText)
190        ) {
191          //Cannot pass through for having either incomplete or invalid fields.
192          var data = xhr.responseJSON
193            ? xhr.responseJSON
194            : JSON.parse(xhr.responseText);
195          data.stepToDisplay = parseInt(data.stepToDisplay);
196          callbackDisplayForm(data);
197        } else {
198          console.log('Failed to post data. ' + error);
199          callbackError('Error: Failed to post form data.');
200        }
201      },
202    });
203  }
204
205  function postBypassFormData(
206    formData,
207    callbackPassThrough,
208    callbackDisplayBypass,
209    callbackDisplayForm,
210    callbackError
211  ) {
212    bbmFormsJQ.ajax({
213      type: 'post',
214      url: BBMForms.constants.apiPath + '/lead/check',
215      data: formData,
216      dataFilter: function () {}, //Empty out dataFilter that may exist.
217      processData: false,
218      contentType: false,
219      success: function (data, status, xhr) {
220        if (!data && (xhr.responseJSON || xhr.responseText))
221          data = xhr.responseJSON
222            ? xhr.responseJSON
223            : JSON.parse(xhr.responseText);
224
225        callbackPassThrough(data);
226      },
227      error: function (xhr, status, error) {
228        if (xhr.status === 200 && (xhr.responseJSON || xhr.responseText)) {
229          //When responseJSON is undefined and fails to parse it comes here. Handle as passthrough.
230          var data = xhr.responseJSON
231            ? xhr.responseJSON
232            : JSON.parse(xhr.responseText);
233          callbackPassThrough(data);
234        } else if (xhr.status === 400 || xhr.status === 404) {
235          callbackDisplayForm(xhr.responseText);
236        } else if (xhr.status === 403) {
237          callbackDisplayBypass(xhr.responseText);
238        } else {
239          console.log('Failed to post data. ' + error);
240          callbackError('Error: Failed to post form data.');
241        }
242      },
243    });
244  }
245
246  function postBypassSerializedForm(
247    formData,
248    callbackPassThrough,
249    callbackDisplayBypass,
250    callbackDisplayForm,
251    callbackError
252  ) {
253    bbmFormsJQ.ajax({
254      type: 'post',
255      url: BBMForms.constants.apiPath + '/lead/check',
256      data: formData,
257      dataFilter: function () {}, //Empty out dataFilter that may exist.
258      success: function (data, status, xhr) {
259        if (!data && (xhr.responseJSON || xhr.responseText))
260          data = xhr.responseJSON
261            ? xhr.responseJSON
262            : JSON.parse(xhr.responseText);
263
264        callbackPassThrough(data);
265      },
266      error: function (xhr, status, error) {
267        if (xhr.status === 200 && (xhr.responseJSON || xhr.responseText)) {
268          //When responseJSON is undefined and fails to parse it comes here. Handle as passthrough.
269          var data = xhr.responseJSON
270            ? xhr.responseJSON
271            : JSON.parse(xhr.responseText);
272          callbackPassThrough(data);
273        } else if (xhr.status === 400 || xhr.status === 404) {
274          callbackDisplayForm(xhr.responseText);
275        } else if (xhr.status === 403) {
276          callbackDisplayBypass(xhr.responseText);
277        } else {
278          console.log('Failed to post data. ' + error);
279          callbackError('Error: Failed to post form data.');
280        }
281      },
282    });
283  }
284
285  function getForm(reqData, callbackPassThrough, callbackGated, callbackError) {
286    bbmFormsJQ.ajax({
287      type: 'get',
288      url: BBMForms.constants.apiPath + '/lead',
289      data: reqData,
290      dataFilter: function () {}, //Empty out dataFilter that may exist.
291      success: function (data, status, xhr) {
292        if (!data && (xhr.responseJSON || xhr.responseText))
293          data = xhr.responseJSON
294            ? xhr.responseJSON
295            : JSON.parse(xhr.responseText);
296
297        callbackPassThrough(data);
298      },
299      error: function (xhr, status, error) {
300        if (xhr.status === 200 && (xhr.responseJSON || xhr.responseText)) {
301          //When responseJSON is undefined and fails to parse it comes here. Handle as passthrough.
302          var data = xhr.responseJSON
303            ? xhr.responseJSON
304            : JSON.parse(xhr.responseText);
305          callbackPassThrough(data);
306        } else if (xhr.status === 404) {
307          //Form is not found
308          callbackError('Not Found: The requested form is not found.');
309        } else if (
310          xhr.status === 403 &&
311          (xhr.responseJSON || xhr.responseText)
312        ) {
313          //Cannot pass through for having incomplete fields.
314          var data = xhr.responseJSON
315            ? xhr.responseJSON
316            : JSON.parse(xhr.responseText);
317          data.stepToDisplay = parseInt(data.stepToDisplay);
318          callbackGated(data);
319        } else {
320          console.log('Get form error: ' + error);
321          callbackError('Error: Failed to retrieve the requested form.');
322        }
323      },
324    });
325  }
326
327  function postResource(
328    reqData,
329    callbackPassThrough,
330    callbackGated,
331    callbackError
332  ) {
333    bbmFormsJQ.ajax({
334      type: 'post',
335      url: BBMForms.constants.apiPath + '/resource',
336      data: reqData,
337      dataFilter: function () {}, //Empty out dataFilter that may exist.
338      success: function (data, status, xhr) {
339        if (!data && (xhr.responseJSON || xhr.responseText))
340          data = xhr.responseJSON
341            ? xhr.responseJSON
342            : JSON.parse(xhr.responseText);
343
344        callbackPassThrough(data);
345      },
346      error: function (xhr, status, error) {
347        if (xhr.status === 200 && (xhr.responseJSON || xhr.responseText)) {
348          //When responseJSON is undefined and fails to parse it comes here. Handle as passthrough.
349          var data = xhr.responseJSON
350            ? xhr.responseJSON
351            : JSON.parse(xhr.responseText);
352          callbackPassThrough(data);
353        } else if (xhr.status === 404) {
354          //Form is not found
355          callbackError('Not Found: The requested form is not found.');
356        } else if (
357          xhr.status === 403 &&
358          (xhr.responseJSON || xhr.responseText)
359        ) {
360          //Cannot pass through for having incomplete fields.
361          var data = xhr.responseJSON
362            ? xhr.responseJSON
363            : JSON.parse(xhr.responseText);
364          data.stepToDisplay = parseInt(data.stepToDisplay);
365          callbackGated(data);
366        } else {
367          console.log('Get form error: ' + error);
368          callbackError('Error: Failed to retrieve the requested form.');
369        }
370      },
371    });
372  }
373
374  function getBootstrapColumnClasses(fieldSize) {
375    switch (fieldSize) {
376      case 'short':
377        return 'col-xs-12 col-sm-3 col-md-3';
378      case 'long':
379        return 'col-xs-12 col-sm-9 col-md-9';
380      case 'full':
381        return 'col-xs-12 col-sm-12 col-md-12';
382      case 'medium':
383      default:
384        return 'col-xs-12 col-sm-6 col-lg-6';
385    }
386  }
387
388  function getBootstrapFieldClasses(fieldSize, independentLine) {
389    if (independentLine && fieldSize !== 'full') {
390      return getBootstrapColumnClasses(fieldSize);
391    } else {
392      return '';
393    }
394  }
395
396  function createFieldBlock(properties, includeLabel) {
397    var $fieldBlock = bbmFormsJQ('<div/>')
398      .addClass(BBMForms.constants.fieldBlockCssClass)
399      .addClass(properties.fieldType);
400    if (properties.independentLine === true) {
401      $fieldBlock.addClass('full').addClass(getBootstrapColumnClasses('full'));
402    } else {
403      $fieldBlock
404        .addClass(properties.fieldSize)
405        .addClass(getBootstrapColumnClasses(properties.fieldSize));
406    }
407
408    includeLabel = typeof includeLabel !== 'undefined' ? includeLabel : true;
409    if (includeLabel) {
410      var required = properties.required
411        ? " <sup class='" +
412          BBMForms.constants.requiredTextCssClass +
413          "'>" +
414          BBMForms.constants.requiredText +
415          '</sup>'
416        : '';
417      var $label = bbmFormsJQ('<label/>', {
418        for: properties.clientProperties.clientId,
419      }).html(properties.label + required);
420      $fieldBlock.append($label);
421    }
422
423    if (properties.instructionText) {
424      var $instTxt = bbmFormsJQ('<p></p>')
425        .text(properties.instructionText)
426        .addClass(BBMForms.constants.instructionTextCssClass);
427      $fieldBlock.append($instTxt);
428    }
429
430    return $fieldBlock;
431  }
432
433  function createPaymentBlock(properties, includeLabel) {
434    var $fieldBlock = bbmFormsJQ('<div/>').addClass('row cart-page');
435
436    // hide required text
437    bbmFormsJQ('.instructionText').html('');
438
439    //Add Payment class to steps wrapper
440    var $stepsWrapper = bbmFormsJQ('.steps_wrapper');
441    $stepsWrapper.addClass('payment');
442
443    if (properties.instructionText) {
444      var $instTxt = bbmFormsJQ('<p></p>')
445        .text(properties.instructionText)
446        .addClass(BBMForms.constants.instructionTextCssClass);
447      $fieldBlock.append($instTxt);
448    }
449
450    return $fieldBlock;
451  }
452
453  function createInputFieldList(properties, inputType) {
454    var $inputListWrap = bbmFormsJQ('<div/>');
455
456    var columnsCssClass = '';
457    if (properties.columns) {
458      switch (properties.columns.toLowerCase()) {
459        case 'two':
460          columnsCssClass = 'col-xs-12 col-sm-6 col-md-6';
461          break;
462        case 'three':
463          columnsCssClass = 'col-xs-12 col-sm-6 col-md-4';
464          break;
465        case 'four':
466          columnsCssClass = 'col-xs-12 col-sm-4 col-md-3';
467          break;
468      }
469    }
470
471    var listIndex = 0;
472    bbmFormsJQ.each(properties.values, function (index, item) {
473      listIndex = index;
474      var inputId = properties.clientProperties.clientId + '_' + listIndex;
475      var value = item.value ? item.value : item.option;
476      var $inputWrap = bbmFormsJQ('<div/>')
477        .addClass(BBMForms.constants.columnCssClass)
478        .addClass(columnsCssClass);
479      if ((inputType == 'radio' || inputType == 'checkbox') && item.image) {
480        //item description requires a different set up (for Detailed Checkboxes)
481        if (!item.description) {
482          $inputWrap.append(
483            bbmFormsJQ('<label/>', { for: inputId })
484              .addClass(BBMForms.constants.imageLabelCssClass)
485              .append(
486                bbmFormsJQ('<img/>', { src: item.image, alt: item.option })
487              )
488          );
489        }
490      }
491      $inputWrap.append(
492        bbmFormsJQ('<input/>', {
493          id: inputId,
494          name: properties.externalId,
495          'data-fieldname': properties.name,
496          type: inputType,
497        }).val(value)
498      );
499
500      //If auto-progression is enabled, auto-click the next btn
501      if (
502        properties.required &&
503        inputType == 'radio' &&
504        properties.enableAutoProgress &&
505        properties.$enableAutoProgressBtn
506      ) {
507        $inputWrap.change(function () {
508          properties.$enableAutoProgressBtn.click();
509        });
510      }
511
512      //Detailed Checkbox
513      if (item.description) {
514        $inputWrap
515          .append(
516            bbmFormsJQ('<label/>', {
517              for: inputId,
518              class: BBMForms.constants.detailedLabelCssClass,
519            }).text(item.option)
520          )
521          .addClass(BBMForms.constants.detailedCheckboxCssClass)
522          .append(
523            bbmFormsJQ('<div/>')
524              .addClass(BBMForms.constants.detailedDescriptionCssClass)
525              .html(item.description)
526          );
527
528        if (item.image) {
529          $inputWrap.append(
530            bbmFormsJQ('<img/>', {
531              src: item.image,
532              alt: item.option,
533            }).addClass(BBMForms.constants.detailedImageCssClass)
534          );
535        }
536
537        $inputWrap.change(function () {
538          bbmFormsJQ(this).toggleClass('selected');
539        });
540      } else {
541        //Default Checkbox/Radio button
542        $inputWrap.append(
543          bbmFormsJQ('<label/>', { for: inputId }).text(item.option)
544        );
545      }
546
547      $inputListWrap.append($inputWrap);
548    });
549
550    var fieldIdOther =
551      properties.clientProperties.clientId +
552      BBMForms.constants.otherFieldIdSuffix;
553    if (properties.other === true) {
554      listIndex++;
555      var inputId = properties.clientProperties.clientId + '_' + listIndex;
556      var $inputWrap = bbmFormsJQ('<div/>')
557        .addClass(BBMForms.constants.columnCssClass)
558        .addClass(columnsCssClass);
559      var $otherInput = bbmFormsJQ('<input/>', {
560        id: inputId,
561        name: properties.externalId,
562        type: inputType,
563      }).val(BBMForms.constants.otherText);
564      $inputWrap.append($otherInput);
565      $inputWrap.append(
566        bbmFormsJQ('<label/>', { for: inputId }).text(
567          BBMForms.constants.otherText
568        )
569      );
570      $inputListWrap.append($inputWrap);
571
572      //Create clientId to access on submit
573      properties.clientProperties.otherFieldClientId = fieldIdOther;
574
575      var $otherBox = bbmFormsJQ('<div/>')
576        .addClass(BBMForms.constants.otherContainerCssClass)
577        .hide();
578      var $otherInstTxt = bbmFormsJQ('<p/>').text(
579        BBMForms.constants.otherExplanationText
580      );
581      var $otherField = bbmFormsJQ('<input/>', {
582        id: fieldIdOther,
583        name: properties.externalId,
584        type: 'text',
585      })
586        .addClass(properties.fieldSize)
587        .addClass(
588          getBootstrapFieldClasses(
589            BBMForms.constants.otherFieldSize,
590            properties.independentLine
591          )
592        )
593        .prop('disabled', true);
594      $otherBox.append($otherInstTxt).append($otherField);
595      $inputListWrap.append($otherBox);
596
597      $inputListWrap
598        .find("input[name='" + properties.externalId + "']")
599        .change(function () {
600          if (
601            $inputListWrap.find("input[id='" + inputId + "']").prop('checked')
602          ) {
603            $otherField.prop('disabled', false);
604            $otherBox.show();
605          } else {
606            $otherBox.hide();
607            $otherField.prop('disabled', true);
608            $otherField.val('');
609          }
610        });
611    }
612
613    return $inputListWrap;
614  }
615
616  function createPaymentInputFieldList(properties) {
617    var $inputListWrap = bbmFormsJQ('<div/>');
618
619    bbmFormsJQ.each(properties.values, function (index, item) {
620      var inputId = properties.clientProperties.clientId + '_' + index;
621
622      //Divs wraps for shopping cart
623      var $inputWrap = bbmFormsJQ('<div/>', {
624        'data-title': item.label,
625      }).addClass('item lineItem');
626      var $blockWrap = bbmFormsJQ('<div/>').addClass('col-10 col-xs-10');
627      var $row = bbmFormsJQ('<div/>').addClass('row');
628      $row.append(
629        bbmFormsJQ('<input/>', {
630          id: inputId,
631          name: properties.externalId,
632          'data-fieldname': properties.name,
633          type: 'checkbox',
634          class: 'col-1 col-xs-1 col-sm-2',
635        }).val(item.value)
636      );
637      var price = parseFloat(item.cost);
638      if (isNaN(price)) {
639        price = 0;
640      }
641      var $priceWrap = bbmFormsJQ('<div/>', { 'data-value': item.cost })
642        .addClass('col-2 col-xs-2 pricing')
643        .text(item.costLabel + price.toFixed(2));
644
645      if (item.image) {
646        var $imageWrap = bbmFormsJQ('<div/>').addClass(
647          'col-sm-2 col-0 col-xs-0 hide-mobile'
648        );
649        $imageWrap.append(
650          bbmFormsJQ('<img/>', { src: item.image, alt: item.label })
651        );
652        $row.append($imageWrap);
653      }
654
655      var $descriptionWrap = bbmFormsJQ('<div/>').addClass(
656        'col-10 col-xs-10 col-sm-8'
657      );
658      $descriptionWrap.append(bbmFormsJQ('<h3/>').text(item.label));
659
660      if (item.description) {
661        $descriptionWrap.append(bbmFormsJQ('<p/>').text(item.description));
662      }
663
664      //Build: append in the correct order
665      $row.append($descriptionWrap);
666      $blockWrap.append($row);
667      $inputWrap.append($blockWrap);
668      $inputWrap.append($priceWrap);
669
670      $inputWrap.change(updatePaymentCart);
671
672      $inputListWrap.append($inputWrap);
673    });
674
675    return $inputListWrap;
676  }
677
678  function updatePaymentCart() {
679    let $cartSummary = bbmFormsJQ('.cart-lineItems').html('');
680    let numTotal = 0.0;
681    let itemTotal = 0;
682    bbmFormsJQ('.lineItem').each(function () {
683      let lineItem = bbmFormsJQ(this).find('input:checked');
684      if (lineItem.length > 0) {
685        itemTotal++;
686        let priceElement = bbmFormsJQ(this).find('.pricing');
687        let lineItemTitle = bbmFormsJQ(this).attr('data-title');
688        let price = parseFloat(priceElement.attr('data-value'));
689        if (isNaN(price)) {
690          price = 0;
691        }
692        numTotal += price;
693
694        //add it to the shopping cart right page as well
695        let $cartItem = bbmFormsJQ('<div/>').addClass(
696          'cart-item row no-gutters'
697        );
698        let $cartTitle = bbmFormsJQ('<h3/>')
699          .addClass('col-7 col-xs-7')
700          .text(lineItemTitle);
701        let $cartPrice = bbmFormsJQ('<div/>')
702          .addClass('col-5 col-xs-5 pricing')
703          .text('$' + price.toFixed(2));
704        $cartItem.append($cartTitle);
705        $cartItem.append($cartPrice);
706        $cartSummary.append($cartItem);
707      }
708    });
709
710    var $checkoutBtn = bbmFormsJQ('.checkoutBtn');
711    if (itemTotal > 0) {
712      $checkoutBtn.text(BBMForms.constants.paymentCheckoutBtnText);
713      $checkoutBtn.prop('disabled', false);
714    } else {
715      $checkoutBtn.text(BBMForms.constants.paymentCheckoutBtnDisableText);
716      $checkoutBtn.prop('disabled', true);
717    }
718
719    bbmFormsJQ('.cart-num').text(itemTotal + ' Added');
720    bbmFormsJQ('.total-pricing').text(
721      BBMForms.defaultOptions.costLabel + numTotal.toFixed(2)
722    );
723  }
724
725  function createSingleLineFormBlock(
726    properties,
727    inputType,
728    userMetadata,
729    attributes
730  ) {
731    var $fieldBlock = createFieldBlock(properties);
732
733    var $input = bbmFormsJQ('<input/>', {
734      id: properties.clientProperties.clientId,
735      name: properties.externalId,
736      'data-fieldname': properties.name,
737      type: inputType,
738    })
739      .addClass(properties.fieldSize)
740      .addClass(
741        getBootstrapFieldClasses(
742          properties.fieldSize,
743          properties.independentLine
744        )
745      );
746    if (properties.characterLimit) {
747      $input.attr('maxlength', properties.characterLimit);
748    }
749    if (properties.fieldMasking) {
750      $input.attr('placeholder', properties.fieldMasking);
751    }
752    if (attributes) {
753      $input.attr(attributes);
754    }
755    $fieldBlock.append($input);
756
757    return $fieldBlock;
758  }
759
760  function createFileFormBlock(properties) {
761    var $fieldBlock = createFieldBlock(properties);
762
763    //Previously uploaded file name will be displayed during prePopulateAndValidate();
764    var $input = bbmFormsJQ('<input/>', {
765      id: properties.clientProperties.clientId,
766      name: properties.externalId,
767      'data-fieldname': properties.name,
768      type: 'file',
769    })
770      .addClass(properties.fieldSize)
771      .addClass(
772        getBootstrapFieldClasses(
773          properties.fieldSize,
774          properties.independentLine
775        )
776      );
777    if (properties.fileTypes) {
778      $input.attr('accept', properties.fileTypes);
779    }
780    $fieldBlock.append($input);
781
782    var $labelBtn = bbmFormsJQ('<label/>', {
783      for: properties.clientProperties.clientId,
784    })
785      .addClass(BBMForms.constants.fileButtonCssClass)
786      .text(BBMForms.constants.fileButtonText)
787      .attr('style', 'background-color:#'.concat(properties.buttonColor, ';'));
788    $fieldBlock.append($labelBtn);
789
790    var $labelFileName = bbmFormsJQ('<span/>').addClass(
791      BBMForms.constants.fileNameCssClass
792    );
793    $fieldBlock.append($labelFileName);
794
795    //Show selected file name
796    $input.change(function () {
797      if (this.files && this.files.length > 0) {
798        var fn = this.files[0].name;
799        $labelFileName.text(fn);
800      } else {
801        $labelFileName.text('');
802      }
803    });
804
805    return $fieldBlock;
806  }
807
808  function createParagraphTextFormBlock(properties, userMetadata) {
809    var $fieldBlock = createFieldBlock(properties);
810    var numRows = properties.rows
811      ? properties.rows
812      : BBMForms.constants.textareaDefaultRows;
813
814    var $input = bbmFormsJQ('<textarea/>', {
815      id: properties.clientProperties.clientId,
816      name: properties.externalId,
817      'data-fieldname': properties.name,
818      rows: numRows,
819    })
820      .addClass(properties.fieldSize)
821      .addClass(
822        getBootstrapFieldClasses(
823          properties.fieldSize,
824          properties.independentLine
825        )
826      );
827    if (properties.characterLimit) {
828      $input.attr('maxlength', properties.characterLimit);
829    }
830    $fieldBlock.append($input);
831
832    return $fieldBlock;
833  }
834
835  function createMultipleChoiceFormBlock(properties, userMetadata) {
836    var $fieldBlock = createFieldBlock(properties);
837
838    var inputType = 'radio';
839    var $inputList = createInputFieldList(properties, inputType);
840    $fieldBlock.append($inputList);
841
842    //This hidden field is added so in cases where the radio buttons is not required, this will submit an empty value
843    var $inputHidden = bbmFormsJQ('<input/>', {
844      type: 'hidden',
845      name: properties.externalId,
846      id:
847        properties.clientProperties.clientId +
848        '_hidden' +
849        BBMForms.constants.multipleRadioButtonNameSuffix,
850    });
851    $fieldBlock.append($inputHidden);
852
853    return $fieldBlock;
854  }
855
856  function createCheckboxesFormBlock(properties, userMetadata) {
857    var $fieldBlock = createFieldBlock(properties);
858
859    var inputType = 'checkbox';
860    var $inputList = createInputFieldList(properties, inputType);
861    $fieldBlock.append($inputList);
862
863    //This hidden field is added so in cases where the checkbox is not required, this will submit an empty value
864    var $inputHidden = bbmFormsJQ('<input/>', {
865      type: 'hidden',
866      name: properties.externalId,
867      id:
868        properties.clientProperties.clientId +
869        '_hidden' +
870        BBMForms.constants.multipleCheckboxNameSuffix,
871    });
872    $fieldBlock.append($inputHidden);
873
874    return $fieldBlock;
875  }
876
877  function createDropdownMenuFormBlock(properties, userMetadata) {
878    var $fieldBlock = createFieldBlock(properties);
879
880    var $select = bbmFormsJQ('<select/>', {
881      id: properties.clientProperties.clientId,
882      name: properties.externalId,
883      'data-fieldname': properties.name,
884    })
885      .addClass(properties.fieldSize)
886      .addClass(
887        getBootstrapFieldClasses(
888          properties.fieldSize,
889          properties.independentLine
890        )
891      );
892
893    //If auto-progression is enabled, auto-click the next btn
894    if (
895      properties.required &&
896      properties.enableAutoProgress &&
897      properties.$enableAutoProgressBtn
898    ) {
899      $select.change(function () {
900        //Keep user on current step so they can fill out the other text box
901        if (this.value === BBMForms.constants.otherText) {
902          return;
903        }
904
905        properties.$enableAutoProgressBtn.click();
906      });
907    }
908
909    $select.append(bbmFormsJQ('<option/>').text('- Select -').val(''));
910    bbmFormsJQ.each(properties.values, function (index, item) {
911      var value = item.value ? item.value : item.option;
912      $select.append(bbmFormsJQ('<option/>').text(item.option).val(value));
913    });
914    $fieldBlock.append($select);
915
916    var fieldIdOther =
917      properties.clientProperties.clientId +
918      BBMForms.constants.otherFieldIdSuffix;
919    if (properties.other === true) {
920      $select.append(
921        bbmFormsJQ('<option/>')
922          .text(BBMForms.constants.otherText)
923          .val(BBMForms.constants.otherText)
924      );
925
926      //Create clientId to access on submit
927      properties.clientProperties.otherFieldClientId = fieldIdOther;
928
929      var $otherBox = bbmFormsJQ('<div/>')
930        .addClass(BBMForms.constants.otherContainerCssClass)
931        .hide();
932      var $otherInstTxt = bbmFormsJQ('<p/>').text(
933        BBMForms.constants.otherExplanationText
934      );
935      var $otherField = bbmFormsJQ('<input/>', {
936        id: fieldIdOther,
937        name: properties.externalId,
938        type: 'text',
939      })
940        .addClass(properties.fieldSize)
941        .addClass(
942          getBootstrapFieldClasses(
943            BBMForms.constants.otherFieldSize,
944            properties.independentLine
945          )
946        )
947        .prop('disabled', true);
948      $otherBox.append($otherInstTxt).append($otherField);
949      $fieldBlock.append($otherBox);
950
951      $select.change(function () {
952        var $otherOption = bbmFormsJQ(this).find(
953          "option[value='" + BBMForms.constants.otherText + "']"
954        );
955        if ($otherOption.is(':selected')) {
956          $otherField.prop('disabled', false);
957          $otherBox.show();
958        } else {
959          $otherBox.hide();
960          $otherField.prop('disabled', true);
961        }
962      });
963    }
964
965    return $fieldBlock;
966  }
967
968  function createConsentCheckboxFormBlock(properties) {
969    var $fieldBlock = createFieldBlock(properties, false);
970
971    var $consentColumn = bbmFormsJQ('<div/>').addClass(
972      BBMForms.constants.columnCssClass
973    );
974
975    //This input is just to interact with the user. Actual value sent to the api is inputHidden below. This is because when unchecked the field is not sent at all.
976    var $input = bbmFormsJQ('<input/>', {
977      id: properties.clientProperties.clientId,
978      name:
979        properties.externalId + BBMForms.constants.consentCheckboxNameSuffix,
980      'data-fieldname': properties.name,
981      type: 'checkbox',
982    })
983      .addClass(properties.fieldSize)
984      .addClass(
985        getBootstrapFieldClasses(
986          properties.fieldSize,
987          properties.independentLine
988        )
989      );
990    $consentColumn.append($input);
991
992    //This hidden field is always there with a value. See updateFormDataForConsentCheckBox.
993    var $inputHidden = bbmFormsJQ('<input/>', {
994      type: 'hidden',
995      name: properties.externalId,
996      id: properties.clientProperties.clientId + '_hidden',
997    });
998    $consentColumn.append($inputHidden);
999
1000    var consentHtml = decodeHtml(properties.label);
1001    $consentColumn.append(consentHtml);
1002
1003    $fieldBlock.append($consentColumn);
1004
1005    return $fieldBlock;
1006  }
1007
1008  function createSectionDividerFormBlock(properties) {
1009    var $fieldBlock = createFieldBlock(properties, false);
1010
1011    var $dividerBlock = bbmFormsJQ('<div/>').addClass(
1012      BBMForms.constants.columnCssClass
1013    );
1014
1015    if (properties.showTopDividerLine) {
1016      $dividerBlock.append("<div class='line-divider col-xs-12'>");
1017    }
1018
1019    var descriptionHtml = decodeHtml(properties.description);
1020    $dividerBlock.append(descriptionHtml);
1021
1022    if (properties.showBottomDividerLine) {
1023      $dividerBlock.append("<div class='line-divider col-xs-12'>");
1024    }
1025
1026    $fieldBlock.append($dividerBlock);
1027
1028    return $fieldBlock;
1029  }
1030
1031  function createSingleLineZipCode(properties, userMetadata) {
1032    var $fieldBlock = createFieldBlock(properties);
1033
1034    var $input = bbmFormsJQ('<input/>', {
1035      id: properties.clientProperties.clientId,
1036      name: properties.externalId,
1037      'data-fieldname': properties.name,
1038      type: 'text',
1039    })
1040      .addClass(properties.fieldSize)
1041      .addClass(
1042        getBootstrapFieldClasses(
1043          properties.fieldSize,
1044          properties.independentLine
1045        )
1046      );
1047    $input.attr('maxlength', '5');
1048    $input.attr('pattern', '[0-9]*');
1049    $fieldBlock.append($input);
1050
1051    return $fieldBlock;
1052  }
1053
1054  function createPaymentCart(properties, userMetadata) {
1055    var $fieldBlock = createPaymentBlock(properties);
1056    var $leftSide = bbmFormsJQ('<div/>')
1057      .addClass('col-12 col-xs-12 col-md-9')
1058      .append(createPaymentInputFieldList(properties));
1059    $fieldBlock.append($leftSide);
1060
1061    //Mobile
1062    var $rightSideMobile = bbmFormsJQ('<div/>').addClass(
1063      'col-12 cold-xs-12 col-lg-3 cart-mobile'
1064    );
1065    $rightSideMobile.append(
1066      bbmFormsJQ('<span/>')
1067        .addClass(BBMForms.constants.paymentCartMobileTotalCssClass)
1068        .text('0 ' + BBMForms.constants.paymentCartMobileTotalText)
1069    );
1070    $fieldBlock.append($rightSideMobile);
1071
1072    //Desktop
1073    var $rightSideDesktop = bbmFormsJQ('<div/>')
1074      .addClass('col-12 col-xs-12 col-md-3 cart-desktop')
1075      .append(bbmFormsJQ('<h4/>').text(BBMForms.constants.paymentCartHeader));
1076    $rightSideDesktop.append(bbmFormsJQ('<div/>').addClass('cart-lineItems'));
1077
1078    //Line Items
1079    var $lineItem = bbmFormsJQ('<div/>').addClass('cart-items');
1080    $rightSideDesktop.append($lineItem);
1081
1082    var $totalWrap = bbmFormsJQ('<div/>').addClass('row no-gutters');
1083    $totalWrap.append(
1084      bbmFormsJQ('<div/>')
1085        .addClass(
1086          'col-6  col-xs-6 ' + BBMForms.constants.paymentCartTotalCssClass
1087        )
1088        .text('Total')
1089    );
1090    $totalWrap.append(
1091      bbmFormsJQ('<div/>')
1092        .addClass(
1093          'col-6  col-xs-6 text-right ' +
1094            BBMForms.constants.paymentCartTotalPriceCssClass
1095        )
1096        .text('$0.00')
1097    );
1098
1099    $rightSideDesktop.append($totalWrap);
1100    $fieldBlock.append($rightSideDesktop);
1101
1102    //pre-populate field value
1103    var preVals;
1104    if (
1105      bbmFormsJQ.isArray(properties.fieldValues) &&
1106      properties.fieldValues.length
1107    ) {
1108      preVals = properties.fieldValues;
1109    }
1110
1111    //If no values prefilled from api
1112    else if (properties.name) {
1113      //#1 Url prefill
1114      var values = getUrlParameter(properties.name);
1115      if (values.length) {
1116        preVals = values;
1117      }
1118      //#2 User metadata prefill
1119      else if (userMetadata) {
1120        var propName = getOwnPropertyNameInsensitive(
1121          userMetadata,
1122          properties.name
1123        );
1124        if (propName) {
1125          var vals = userMetadata[propName];
1126          if (vals) {
1127            if (bbmFormsJQ.isArray(vals) && vals.length) {
1128              preVals = vals;
1129            } else {
1130              preVals = [vals];
1131            }
1132          }
1133        }
1134      }
1135    }
1136    if (bbmFormsJQ.isArray(preVals) && preVals.length) {
1137      bbmFormsJQ('.cart-page').ready(updatePaymentCart);
1138      //lowercase values for case-insensitive matching
1139      var lowerPreVals = bbmFormsJQ.map(preVals, function (n, i) {
1140        return n.toLowerCase();
1141      });
1142      var matchingValues = $leftSide
1143        .find('input[type=checkbox]')
1144        .filter(function () {
1145          var $option = bbmFormsJQ(this);
1146          var optionVal = $option.attr('value').toLowerCase();
1147          var matchingVal = '';
1148          bbmFormsJQ.each(lowerPreVals, function (index, value) {
1149            if (value == optionVal) {
1150              matchingVal = value;
1151              //select the matching option
1152              $option.prop('checked', true).trigger('change');
1153            }
1154          });
1155          return matchingVal.length > 0;
1156        })
1157        .map(function () {
1158          return bbmFormsJQ(this).attr('value').toLowerCase();
1159        });
1160    }
1161
1162    //This hidden field is added so in cases where the checkbox is not required, this will submit an empty value
1163    var $inputHidden = bbmFormsJQ('<input/>', {
1164      type: 'hidden',
1165      name: properties.externalId,
1166      id:
1167        properties.clientProperties.clientId +
1168        '_hidden' +
1169        BBMForms.constants.multipleCheckboxNameSuffix,
1170    });
1171    $fieldBlock.append($inputHidden);
1172
1173    //Validation
1174    if (properties.validationError) {
1175      $fieldBlock.append(
1176        bbmFormsJQ('<span/>')
1177          .text(properties.validationError)
1178          .addClass(BBMForms.constants.errorMsgCssClass)
1179      );
1180    }
1181
1182    bbmFormsJQ('.buttons').hide();
1183
1184    return $fieldBlock;
1185  }
1186
1187  function configureConditionalDisplay(self, properties) {
1188    //Handle IConditionalDisplayField
1189    if (
1190      properties.conditionalDisplayBaseFieldId &&
1191      properties.conditionalDisplayValues &&
1192      bbmFormsJQ.isArray(properties.conditionalDisplayValues) &&
1193      properties.conditionalDisplayValues.length
1194    ) {
1195      var $field =
1196        properties.fieldType == 'MultipleChoice' ||
1197        properties.fieldType == 'Checkboxes'
1198          ? bbmFormsJQ("input[name='" + properties.externalId + "']")
1199          : bbmFormsJQ('#' + properties.clientProperties.clientId);
1200      var $fieldBlock = $field.closest(
1201        '.' + BBMForms.constants.fieldBlockCssClass
1202      );
1203      if ($fieldBlock) {
1204        properties.display = false;
1205        bbmFormsJQ.each(self.formData.form.steps, function (index, step) {
1206          var baseFieldArr = bbmFormsJQ.grep(step, function (item) {
1207            return item.externalId === properties.conditionalDisplayBaseFieldId;
1208          });
1209          if (baseFieldArr && baseFieldArr.length) {
1210            var baseField = baseFieldArr[0];
1211            var $baseField =
1212              baseField.fieldType == 'MultipleChoice' ||
1213              baseField.fieldType == 'Checkboxes'
1214                ? bbmFormsJQ("input[name='" + baseField.externalId + "']")
1215                : bbmFormsJQ('#' + baseField.clientProperties.clientId);
1216            var findIndexArr = bbmFormsJQ.grep(
1217              BBMForms.conditionalDisplayChangeEvents,
1218              function (item) {
1219                return (
1220                  item.baseFieldId ==
1221                    properties.conditionalDisplayBaseFieldId &&
1222                  item.conditionalFieldId == properties.externalId
1223                );
1224              }
1225            );
1226            if ($baseField && (!findIndexArr || findIndexArr.length == 0)) {
1227              $baseField.change(function () {
1228                updateFormDataFromInputForSingleField(baseField);
1229                configureConditionalDisplay(self, properties);
1230              });
1231              var log = {
1232                baseFieldId: properties.conditionalDisplayBaseFieldId,
1233                conditionalFieldId: properties.externalId,
1234              };
1235              BBMForms.conditionalDisplayChangeEvents.push(log);
1236            }
1237            //use checkvalues to determine display if base field is in previous step as we may not have submitted value in the same session.
1238            var checkValues =
1239              baseField.stepNumber < properties.stepNumber
1240                ? baseField.fieldCheckValues
1241                : baseField.fieldValues;
1242            if (checkValues && checkValues.length > 0) {
1243              var intersection = checkValues.filter(function (value) {
1244                return (
1245                  properties.conditionalDisplayValues
1246                    .map(function (x) {
1247                      return x.value;
1248                    })
1249                    .indexOf(value) >= 0
1250                );
1251              });
1252              if (intersection && intersection.length > 0) {
1253                properties.display = true;
1254                return false; //break
1255              }
1256            }
1257          }
1258        });
1259        if (properties.display) $fieldBlock.show();
1260        else $fieldBlock.hide();
1261      }
1262    } else if (
1263      typeof properties.fieldRules != 'undefined' &&
1264      properties.fieldRules.length
1265    ) {
1266      var $field =
1267        properties.fieldType == 'MultipleChoice' ||
1268        properties.fieldType == 'Checkboxes'
1269          ? bbmFormsJQ("input[name='" + properties.externalId + "']")
1270          : bbmFormsJQ('#' + properties.clientProperties.clientId);
1271      var $fieldBlock = $field.closest(
1272        '.' + BBMForms.constants.fieldBlockCssClass
1273      );
1274      if ($fieldBlock) {
1275        properties.display = false;
1276        bbmFormsJQ.each(properties.fieldRules, function (index, rule) {
1277          if (
1278            typeof rule !== 'undefined' &&
1279            typeof rule.fieldID !== 'undefined' &&
1280            typeof rule.options !== 'undefined' &&
1281            rule.options.length > 0
1282          ) {
1283            bbmFormsJQ.each(self.formData.form.steps, function (index, step) {
1284              var baseFieldArr = bbmFormsJQ.grep(step, function (item) {
1285                return item.externalId === rule.fieldID;
1286              });
1287              if (baseFieldArr && baseFieldArr.length) {
1288                var baseField = baseFieldArr[0];
1289                var $baseField =
1290                  baseField.fieldType == 'MultipleChoice' ||
1291                  baseField.fieldType == 'Checkboxes'
1292                    ? bbmFormsJQ("input[name='" + baseField.externalId + "']")
1293                    : bbmFormsJQ('#' + baseField.clientProperties.clientId);
1294                var findIndexArr = bbmFormsJQ.grep(
1295                  BBMForms.conditionalDisplayChangeEvents,
1296                  function (item) {
1297                    return (
1298                      item.baseFieldId == rule.fieldID &&
1299                      item.conditionalFieldId == properties.externalId
1300                    );
1301                  }
1302                );
1303                if ($baseField && (!findIndexArr || findIndexArr.length == 0)) {
1304                  $baseField.change(function () {
1305                    updateFormDataFromInputForSingleField(baseField);
1306                    configureConditionalDisplay(self, properties);
1307                  });
1308                  var log = {
1309                    baseFieldId: rule.fieldID,
1310                    conditionalFieldId: properties.externalId,
1311                  };
1312                  BBMForms.conditionalDisplayChangeEvents.push(log);
1313                }
1314                //use checkvalues to determine display if base field is in previous step as we may not have submitted value in the same session.
1315                var checkValues =
1316                  baseField.stepNumber < properties.stepNumber
1317                    ? baseField.fieldCheckValues
1318                    : baseField.fieldValues;
1319                if (checkValues && checkValues.length > 0) {
1320                  var intersection = checkValues.filter(function (value) {
1321                    return (
1322                      rule.options
1323                        .filter(function (option) {
1324                          return option.fieldDisplay == true;
1325                        })
1326                        .map(function (x) {
1327                          return x.fieldValue;
1328                        })
1329                        .indexOf(value) >= 0
1330                    );
1331                  });
1332                  if (intersection && intersection.length > 0) {
1333                    properties.display = true;
1334                    return false; //break
1335                  }
1336                }
1337              }
1338            });
1339          }
1340        });
1341        if (properties.display) $fieldBlock.show();
1342        else $fieldBlock.hide();
1343      }
1344    }
1345  }
1346
1347  function prePopulateAndValidate(self, properties) {
1348    let userMetadata = self.options.userMetadata;
1349    switch (properties.fieldType) {
1350      case 'SingleLineText':
1351      case 'SingleLineEmailAddress':
1352      case 'SingleLineURL':
1353      case 'SingleLineDate':
1354      case 'SingleLineNumber':
1355      case 'SingleLinePhoneNumber':
1356      case 'ParagraphText':
1357      case 'SingleLineZipCode': {
1358        let $input = bbmFormsJQ('#' + properties.clientProperties.clientId);
1359        let $fieldBlock = $input.closest(
1360          '.' + BBMForms.constants.fieldBlockCssClass
1361        );
1362
1363        //pre-populate field value
1364        let preVal;
1365        if (
1366          bbmFormsJQ.isArray(properties.fieldValues) &&
1367          properties.fieldValues.length &&
1368          properties.fieldValues[0]
1369        ) {
1370          preVal = properties.fieldValues[0];
1371        }
1372        //If no values prefilled from api
1373        else if (properties.name) {
1374          //#1 Url prefill
1375          let values = getUrlParameter(properties.name);
1376          if (values.length) {
1377            preVal = values[0];
1378          }
1379          //#2 User metadata prefill
1380          else if (userMetadata) {
1381            let propName = getOwnPropertyNameInsensitive(
1382              userMetadata,
1383              properties.name
1384            );
1385            if (propName) {
1386              let preVals = userMetadata[propName];
1387              if (bbmFormsJQ.isArray(preVals)) preVal = preVals[0];
1388              else preVal = preVals;
1389            }
1390          }
1391        }
1392        if (preVal) {
1393          $input.val(preVal);
1394        }
1395
1396        //Validation
1397        if (properties.validationError) {
1398          $input.addClass(BBMForms.constants.errorFieldCssClass);
1399          $fieldBlock.append(
1400            bbmFormsJQ('<span/>')
1401              .text(properties.validationError)
1402              .addClass(BBMForms.constants.errorMsgCssClass)
1403          );
1404        }
1405
1406        break;
1407      }
1408      case 'FileUpload': {
1409        let $input = bbmFormsJQ('#' + properties.clientProperties.clientId);
1410        let $fieldBlock = $input.closest(
1411          '.' + BBMForms.constants.fieldBlockCssClass
1412        );
1413
1414        //pre-populate field value
1415        //If previously uploaded file is found, it will display the file name and not display the upload input field.
1416        if (
1417          bbmFormsJQ.isArray(properties.fieldValues) &&
1418          properties.fieldValues.length &&
1419          properties.fieldValues[0]
1420        ) {
1421          let fileUrl = properties.fieldValues[0];
1422          let fileName = fileUrl.split('/').pop();
1423          let $fileTxt = bbmFormsJQ('<p></p>')
1424            .text('Uploaded file: ')
1425            .addClass(BBMForms.constants.instructionTextCssClass)
1426            .append(
1427              bbmFormsJQ('<a/>', { href: fileUrl, target: '_blank' }).text(
1428                fileName
1429              )
1430            );
1431          $fieldBlock.append($fileTxt);
1432        }
1433
1434        //Validation
1435        if (properties.validationError) {
1436          $input.addClass(BBMForms.constants.errorFieldCssClass);
1437          $fieldBlock.append(
1438            bbmFormsJQ('<span/>')
1439              .text(properties.validationError)
1440              .addClass(BBMForms.constants.errorMsgCssClass)
1441          );
1442        }
1443
1444        break;
1445      }
1446      case 'MultipleChoice': {
1447        let $input = bbmFormsJQ("input[name='" + properties.externalId + "']");
1448        let $fieldBlock = $input.closest(
1449          '.' + BBMForms.constants.fieldBlockCssClass
1450        );
1451        let inputType = 'radio';
1452
1453        //pre-populate field value
1454        var preVal;
1455        if (
1456          bbmFormsJQ.isArray(properties.fieldValues) &&
1457          properties.fieldValues.length &&
1458          properties.fieldValues[0]
1459        ) {
1460          preVal = properties.fieldValues[0];
1461        }
1462        //If no values prefilled from api
1463        else if (properties.name) {
1464          //#1 Url prefill
1465          var values = getUrlParameter(properties.name);
1466          if (values.length) {
1467            preVal = values[0];
1468          }
1469          //#2 User metadata prefill
1470          else if (userMetadata) {
1471            var propName = getOwnPropertyNameInsensitive(
1472              userMetadata,
1473              properties.name
1474            );
1475            if (propName) {
1476              var preVals = userMetadata[propName];
1477              if (bbmFormsJQ.isArray(preVals)) preVal = preVals[0];
1478              else preVal = preVals;
1479            }
1480          }
1481        }
1482        if (preVal) {
1483          preVal = preVal.replace('"', '\\"').replace("'", "\\'");
1484          var lowerPreVal = preVal.toLowerCase();
1485          var matchingValue = $fieldBlock
1486            .find("input[type='" + inputType + "']")
1487            .filter(function () {
1488              var $option = bbmFormsJQ(this);
1489              var optionVal = $option.attr('value').toLowerCase();
1490              var matchingVal = '';
1491              if (lowerPreVal == optionVal) {
1492                matchingVal = optionVal;
1493                //select the matching option
1494                $option.prop('checked', true).trigger('change');
1495              }
1496              return matchingVal.length > 0;
1497            })
1498            .map(function () {
1499              return bbmFormsJQ(this).attr('value').toLowerCase();
1500            });
1501          if (properties.other === true && matchingValue.length == 0) {
1502            //if other option is there set non-matching value to the other text box.
1503            $fieldBlock
1504              .find(
1505                "input[type='" +
1506                  inputType +
1507                  "'][value='" +
1508                  BBMForms.constants.otherText +
1509                  "']"
1510              )
1511              .prop('checked', true)
1512              .trigger('change');
1513            $fieldBlock
1514              .find('input#' + properties.clientProperties.otherFieldClientId)
1515              .val(preVal)
1516              .prop('disabled', false);
1517            $fieldBlock
1518              .find('input#' + properties.clientProperties.otherFieldClientId)
1519              .closest('.' + BBMForms.constants.otherContainerCssClass)
1520              .show();
1521          }
1522        }
1523
1524        //Validation
1525        if (properties.validationError) {
1526          $fieldBlock.append(
1527            bbmFormsJQ('<span/>')
1528              .text(properties.validationError)
1529              .addClass(BBMForms.constants.errorMsgCssClass)
1530          );
1531        }
1532
1533        break;
1534      }
1535      case 'Checkboxes': {
1536        let $input = bbmFormsJQ("input[name='" + properties.externalId + "']");
1537        let $fieldBlock = $input.closest(
1538          '.' + BBMForms.constants.fieldBlockCssClass
1539        );
1540        let inputType = 'checkbox';
1541
1542        //pre-populate field value
1543        let preVals;
1544        if (
1545          bbmFormsJQ.isArray(properties.fieldValues) &&
1546          properties.fieldValues.length
1547        ) {
1548          preVals = properties.fieldValues;
1549        }
1550        //If no values prefilled from api
1551        else if (properties.name) {
1552          //#1 Url prefill
1553          let values = getUrlParameter(properties.name);
1554          if (values.length) {
1555            preVals = values;
1556          }
1557          //#2 User metadata prefill
1558          else if (userMetadata) {
1559            let propName = getOwnPropertyNameInsensitive(
1560              userMetadata,
1561              properties.name
1562            );
1563            if (propName) {
1564              let vals = userMetadata[propName];
1565              if (vals) {
1566                if (bbmFormsJQ.isArray(vals) && vals.length) {
1567                  preVals = vals;
1568                } else {
1569                  preVals = [vals];
1570                }
1571              }
1572            }
1573          }
1574        }
1575        if (bbmFormsJQ.isArray(preVals) && preVals.length) {
1576          //lowercase values for case-insensitive matching
1577          let lowerPreVals = bbmFormsJQ.map(preVals, function (n, i) {
1578            return n.toLowerCase();
1579          });
1580          let matchingValues = $fieldBlock
1581            .find("input[type='" + inputType + "']")
1582            .filter(function () {
1583              let $option = bbmFormsJQ(this);
1584              let optionVal = $option.attr('value').toLowerCase();
1585              let matchingVal = '';
1586              bbmFormsJQ.each(lowerPreVals, function (index, value) {
1587                if (value == optionVal) {
1588                  matchingVal = value;
1589                  //select the matching option
1590                  $option.prop('checked', true).trigger('change');
1591                  if ($option.parent().hasClass('detail-checkbox')) {
1592                    $option.parent().addClass('selected');
1593                  }
1594                }
1595              });
1596              return matchingVal.length > 0;
1597            })
1598            .map(function () {
1599              return bbmFormsJQ(this).attr('value').toLowerCase();
1600            });
1601          if (properties.other === true) {
1602            //if other option is there set non-matching value to the other text box.
1603            let nonMatchingValues = lowerPreVals.filter(function (v) {
1604              return bbmFormsJQ.inArray(v, matchingValues) < 0;
1605            });
1606            if (nonMatchingValues.length) {
1607              let value = nonMatchingValues[0]
1608                .replace('"', '\\"')
1609                .replace("'", "\\'");
1610              $fieldBlock
1611                .find(
1612                  "input[type='" +
1613                    inputType +
1614                    "'][value='" +
1615                    BBMForms.constants.otherText +
1616                    "']"
1617                )
1618                .prop('checked', true)
1619                .trigger('change');
1620              $fieldBlock
1621                .find('input#' + properties.clientProperties.otherFieldClientId)
1622                .val(value)
1623                .prop('disabled', false);
1624              $fieldBlock
1625                .find('input#' + properties.clientProperties.otherFieldClientId)
1626                .closest('.' + BBMForms.constants.otherContainerCssClass)
1627                .show();
1628            }
1629          }
1630        }
1631
1632        //Validation
1633        if (properties.validationError) {
1634          $fieldBlock.append(
1635            bbmFormsJQ('<span/>')
1636              .text(properties.validationError)
1637              .addClass(BBMForms.constants.errorMsgCssClass)
1638          );
1639        }
1640
1641        break;
1642      }
1643      case 'DropdownMenu': {
1644        let $input = bbmFormsJQ('#' + properties.clientProperties.clientId);
1645        let $fieldBlock = $input.closest(
1646          '.' + BBMForms.constants.fieldBlockCssClass
1647        );
1648
1649        //pre-populate field value
1650        let preVals;
1651        if (
1652          bbmFormsJQ.isArray(properties.fieldValues) &&
1653          properties.fieldValues.length
1654        ) {
1655          preVals = properties.fieldValues;
1656        }
1657        //If no values prefilled from api
1658        else if (properties.name) {
1659          //#1 Url prefill
1660          let values = getUrlParameter(properties.name);
1661          if (values.length) {
1662            preVals = values;
1663          }
1664          //#2 User metadata prefill
1665          else if (userMetadata) {
1666            let propName = getOwnPropertyNameInsensitive(
1667              userMetadata,
1668              properties.name
1669            );
1670            if (propName) {
1671              let vals = userMetadata[propName];
1672              if (vals) {
1673                if (bbmFormsJQ.isArray(vals) && vals.length) {
1674                  preVals = vals;
1675                } else {
1676                  preVals = [vals];
1677                }
1678              }
1679            }
1680          }
1681        }
1682        if (bbmFormsJQ.isArray(preVals) && preVals.length) {
1683          //lowercase values for case-insensitive matching
1684          let lowerPreVals = bbmFormsJQ.map(preVals, function (n, i) {
1685            return n.toLowerCase();
1686          });
1687          let matchingValues = $input
1688            .find('option')
1689            .filter(function () {
1690              let $option = bbmFormsJQ(this);
1691              let optionVal = $option.attr('value').toLowerCase();
1692              let matchingVal = '';
1693              bbmFormsJQ.each(lowerPreVals, function (index, value) {
1694                if (value == optionVal) {
1695                  matchingVal = value;
1696                  //select the matching option
1697                  $option.prop('selected', true).trigger('change');
1698                }
1699              });
1700              return matchingVal.length > 0;
1701            })
1702            .map(function () {
1703              return bbmFormsJQ(this).attr('value').toLowerCase();
1704            });
1705          if (properties.other === true) {
1706            //if other option is there set non-matching value to the other text box.
1707            let nonMatchingValues = lowerPreVals.filter(function (v) {
1708              return bbmFormsJQ.inArray(v, matchingValues) < 0;
1709            });
1710            if (nonMatchingValues.length) {
1711              let value = nonMatchingValues[0]
1712                .replace('"', '\\"')
1713                .replace("'", "\\'");
1714              $input
1715                .find("option[value='" + BBMForms.constants.otherText + "']")
1716                .prop('selected', true)
1717                .trigger('change');
1718              $fieldBlock
1719                .find('input#' + properties.clientProperties.otherFieldClientId)
1720                .val(value)
1721                .prop('disabled', false);
1722              $fieldBlock
1723                .find('input#' + properties.clientProperties.otherFieldClientId)
1724                .closest('.' + BBMForms.constants.otherContainerCssClass)
1725                .show();
1726            }
1727          }
1728        }
1729
1730        //Validation
1731        if (properties.validationError) {
1732          $input.addClass(BBMForms.constants.errorFieldCssClass);
1733          $fieldBlock.append(
1734            bbmFormsJQ('<span/>')
1735              .text(properties.validationError)
1736              .addClass(BBMForms.constants.errorMsgCssClass)
1737          );
1738        }
1739
1740        break;
1741      }
1742      case 'ConsentCheckbox': {
1743        let $input = bbmFormsJQ('#' + properties.clientProperties.clientId);
1744        let $fieldBlock = $input.closest(
1745          '.' + BBMForms.constants.fieldBlockCssClass
1746        );
1747
1748        //pre-populate field value
1749        if (
1750          bbmFormsJQ.isArray(properties.fieldValues) &&
1751          properties.fieldValues.length &&
1752          properties.fieldValues[0] &&
1753          properties.fieldValues[0].toUpperCase() === 'YES'
1754        ) {
1755          $input.prop('checked', true).trigger('change');
1756        }
1757
1758        //Validation
1759        if (properties.validationError) {
1760          $fieldBlock.append(
1761            bbmFormsJQ('<span/>')
1762              .text(properties.validationError)
1763              .addClass(BBMForms.constants.errorMsgCssClass)
1764          );
1765        }
1766
1767        break;
1768      }
1769    }
1770  }
1771
1772  function createProgressIndicator(progressType, stepNum, stepsCount) {
1773    switch (progressType) {
1774      case 'Steps': {
1775        var $progInd = bbmFormsJQ('<ul/>')
1776          .addClass(BBMForms.constants.progressIndicatorCssClass)
1777          .addClass('align-center padding-bottom-30');
1778        for (var i = 0; i < stepsCount; i++) {
1779          var sn = i + 1;
1780          var $step = bbmFormsJQ('<li/>').text(
1781            BBMForms.constants.stepText + ' ' + sn
1782          );
1783          if (sn == stepNum) {
1784            $step.addClass(BBMForms.constants.progressCurrentStepCssClass);
1785          }
1786          $progInd.append($step);
1787        }
1788        return $progInd;
1789      }
1790      case 'Bar': {
1791        var percentage = Math.round(((stepNum - 1) / stepsCount) * 100);
1792        var $progInd = bbmFormsJQ('<div/>')
1793          .addClass(BBMForms.constants.progressIndicatorCssClass)
1794          .addClass('col-xs-8 col-sm-4 col-md-4 col-center padding-bottom-20');
1795        var $container = bbmFormsJQ('<div/>', {
1796          role: 'progressbar',
1797          'aria-valuenow': percentage,
1798          'aria-valuemin': 0,
1799          'aria-valuemax': 100,
1800        })
1801          .addClass(BBMForms.constants.progressBarContainerCssClass)
1802          .addClass('margin-bottom-10');
1803        var $bar = bbmFormsJQ('<div/>')
1804          .addClass(BBMForms.constants.progressBarCssClass)
1805          .css('width', percentage + '%');
1806        $container.append($bar);
1807        $progInd.append($container);
1808        $progInd.append(bbmFormsJQ('<span/>').text(percentage + '%'));
1809        return $progInd;
1810      }
1811      case 'StepsWithProgressBar': {
1812        var percentage = Math.round(((stepNum - 1) / stepsCount) * 100);
1813        var $progInd = bbmFormsJQ('<div/>')
1814          .addClass(BBMForms.constants.progressIndicatorCssClass)
1815          .addClass('col-xs-8 col-sm-4 col-md-4 col-center padding-bottom-20');
1816        var $container = bbmFormsJQ('<div/>', {
1817          role: 'progressbar',
1818          'aria-valuenow': percentage,
1819          'aria-valuemin': 0,
1820          'aria-valuemax': 100,
1821        })
1822          .addClass(BBMForms.constants.progressBarContainerCssClass)
1823          .addClass('margin-bottom-10');
1824        var $bar = bbmFormsJQ('<div/>')
1825          .addClass(BBMForms.constants.progressBarCssClass)
1826          .css('width', percentage + '%');
1827        $container.append($bar);
1828        $progInd.append($container);
1829        $progInd.append(
1830          bbmFormsJQ('<span/>').text('Step ' + stepNum + ' of ' + stepsCount)
1831        );
1832        return $progInd;
1833      }
1834    }
1835  }
1836
1837  function updateFormDataForSingleValue(properties) {
1838    properties.fieldValues = [];
1839    var value = bbmFormsJQ('#' + properties.clientProperties.clientId).val();
1840    if (value) {
1841      properties.fieldValues.push(value);
1842    }
1843  }
1844
1845  function updateFormDataForFile(properties) {
1846    properties.fieldValues = [];
1847    if (bbmFormsJQ('#' + properties.clientProperties.clientId)[0]) {
1848      var value = bbmFormsJQ('#' + properties.clientProperties.clientId)[0]
1849        .files[0];
1850      if (value) {
1851        properties.fieldValues.push(value);
1852      }
1853    }
1854  }
1855
1856  function updateFormDataForDropDown(properties) {
1857    properties.fieldValues = [];
1858    var value = bbmFormsJQ('#' + properties.clientProperties.clientId).val();
1859
1860    //Use other field value if "other" is selected from dropdown
1861    if (properties.other === true && value === BBMForms.constants.otherText) {
1862      value = bbmFormsJQ(
1863        '#' + properties.clientProperties.otherFieldClientId
1864      ).val();
1865    }
1866
1867    if (value) {
1868      properties.fieldValues.push(value);
1869    }
1870  }
1871
1872  function updateFormDataForRadio(properties) {
1873    properties.fieldValues = [];
1874    var value = bbmFormsJQ(
1875      "input[type='radio'][name='" + properties.externalId + "']:checked"
1876    ).val();
1877
1878    //Use other field value if "other" is selected from dropdown
1879    if (properties.other === true && value === BBMForms.constants.otherText) {
1880      value = bbmFormsJQ(
1881        '#' + properties.clientProperties.otherFieldClientId
1882      ).val();
1883    }
1884
1885    if (value) {
1886      properties.fieldValues.push(value);
1887    }
1888  }
1889
1890  function updateFormDataForCheckBox(properties) {
1891    properties.fieldValues = [];
1892    bbmFormsJQ(
1893      "input[type='checkbox'][name='" + properties.externalId + "']:checked"
1894    ).each(function () {
1895      var value = bbmFormsJQ(this).val();
1896
1897      //Use other field value if "other" is selected from dropdown
1898      if (properties.other === true && value === BBMForms.constants.otherText) {
1899        value = bbmFormsJQ(
1900          '#' + properties.clientProperties.otherFieldClientId
1901        ).val();
1902      }
1903
1904      if (value) {
1905        properties.fieldValues.push(value);
1906      }
1907    });
1908  }
1909
1910  function updateFormDataForConsentCheckBox(properties) {
1911    properties.fieldValues = [];
1912    var $field = bbmFormsJQ('#' + properties.clientProperties.clientId);
1913    var $fieldHidden = bbmFormsJQ(
1914      '#' + properties.clientProperties.clientId + '_hidden'
1915    );
1916    var checked = $field.prop('checked');
1917    var $fieldBlock = $field.closest(
1918      '.' + BBMForms.constants.fieldBlockCssClass
1919    );
1920    var value = '';
1921    if ($fieldBlock.is(':visible')) {
1922      value = checked ? 'Yes' : 'No';
1923    }
1924    properties.fieldValues.push(value);
1925    $fieldHidden.val(value);
1926  }
1927
1928  function displayBypass(self, errMsg) {
1929    self.$formBypass.empty();
1930    self.$formBypass.append(
1931      bbmFormsJQ('<p/>')
1932        .css('text-align', 'right')
1933        .append(
1934          bbmFormsJQ('<a/>')
1935            .attr('href', 'javascript:void(0)')
1936            .text('Back')
1937            .click(function () {
1938              self.display();
1939            })
1940        )
1941    );
1942    if (self.formData.form.advancedOptions.formBypassDescription) {
1943      self.$formBypass.append(
1944        bbmFormsJQ('<p/>')
1945          .css('text-align', 'left')
1946          .text(self.formData.form.advancedOptions.formBypassDescription)
1947      );
1948    }
1949
1950    self.$errorMessage.empty().hide();
1951
1952    if (!self.formData.form.advancedOptions.formBypassFieldId) {
1953      self.$errorMessage
1954        .html(bbmFormsJQ('<p/>').text('Unique field is undefined.'))
1955        .show();
1956      return;
1957    }
1958
1959    var bypassField = findFieldByExternalId(
1960      self.formData.form.steps,
1961      self.formData.form.advancedOptions.formBypassFieldId
1962    );
1963    if (!bypassField) {
1964      self.$errorMessage
1965        .html(bbmFormsJQ('<p/>').text('Unique field is not found.'))
1966        .show();
1967      return;
1968    }
1969
1970    self.$progressIndicatorContainer.html('');
1971
1972    if (errMsg) {
1973      self.$errorMessage.html(bbmFormsJQ('<p/>').text(errMsg)).show();
1974    }
1975
1976    //Empty out all steps.
1977    self.$stepsWrapper.empty();
1978
1979    //Create new step container
1980    var stepContainerId = self.$form.attr('id') + '_bypass';
1981    self.$currentStepContainer = bbmFormsJQ('<div/>', {
1982      id: stepContainerId,
1983    }).addClass(BBMForms.constants.stepContainerCssClass);
1984    self.$stepsWrapper.append(self.$currentStepContainer);
1985
1986    addField(self.$currentStepContainer, bypassField);
1987
1988    var $buttonsContainer = bbmFormsJQ('<div/>')
1989      .addClass(BBMForms.constants.buttonsContainerCssClass)
1990      .addClass(getBootstrapColumnClasses('full'));
1991    self.$currentStepContainer.append($buttonsContainer);
1992
1993    //If this is a multi-step form with more steps to go, show the continue button.
1994    var nextBtnTxt = self.formData.form.advancedOptions.actionButtonText
1995      ? self.formData.form.advancedOptions.actionButtonText
1996      : BBMForms.constants.submitButtonTextDefault;
1997    var nextBtnColor = self.formData.form.advancedOptions.buttonColor
1998      ? self.formData.form.advancedOptions.buttonColor
1999      : BBMForms.constants.submitButtonColorDefault;
2000    var $nextBtn = bbmFormsJQ('<button/>')
2001      .text(nextBtnTxt)
2002      .attr('title', nextBtnTxt)
2003      .addClass(BBMForms.constants.nextButtonCssClass)
2004      .attr('style', 'background-color:#'.concat(nextBtnColor, ';'))
2005      .click(function (e) {
2006        e.preventDefault();
2007
2008        //Disable button to prevent multiple clicks.
2009        var $allBtns = $buttonsContainer.find('button').prop('disabled', true);
2010        var $btn = bbmFormsJQ(this).text('Processing...');
2011        bypassForm(self);
2012      });
2013    $buttonsContainer.append($nextBtn);
2014
2015    if (self.formData.form.advancedOptions.disclaimer) {
2016      //wysiwyg
2017      var disclaimerHtml = decodeHtml(
2018        self.formData.form.advancedOptions.disclaimer
2019      );
2020      var $disclaimer = bbmFormsJQ('<div/>')
2021        .addClass(BBMForms.constants.disclaimerCssClass)
2022        .addClass(getBootstrapColumnClasses('full'))
2023        .html(disclaimerHtml);
2024      self.$currentStepContainer.append($disclaimer);
2025    }
2026
2027    if (self.options.callbackBypassStepLoaded) {
2028      self.options.callbackBypassStepLoaded();
2029    }
2030  }
2031
2032  function findFieldByExternalId(steps, externalId) {
2033    for (var i = 0, lenI = steps.length; i < lenI; i++) {
2034      for (var j = 0, lenJ = steps[i].length; j < lenJ; j++) {
2035        if (steps[i][j].externalId == externalId) return steps[i][j];
2036      }
2037    }
2038    return null;
2039  }
2040
2041  function addField(self, item) {
2042    var $container = self.$currentStepContainer;
2043    var $fieldBlock;
2044    switch (item.fieldType) {
2045      case 'SingleLineText':
2046        $fieldBlock = createSingleLineFormBlock(
2047          item,
2048          'text',
2049          self.options.userMetadata
2050        );
2051        break;
2052      case 'SingleLineEmailAddress':
2053        $fieldBlock = createSingleLineFormBlock(
2054          item,
2055          'email',
2056          self.options.userMetadata
2057        );
2058        break;
2059      case 'SingleLineURL':
2060        $fieldBlock = createSingleLineFormBlock(
2061          item,
2062          'url',
2063          self.options.userMetadata
2064        );
2065        break;
2066      case 'SingleLineDate':
2067        $fieldBlock = createSingleLineFormBlock(
2068          item,
2069          'date',
2070          self.options.userMetadata
2071        );
2072        break;
2073      case 'SingleLineNumber':
2074        $fieldBlock = createSingleLineFormBlock(
2075          item,
2076          'number',
2077          self.options.userMetadata,
2078          { min: 0, step: 1 }
2079        );
2080        break;
2081      case 'SingleLinePhoneNumber':
2082        $fieldBlock = createSingleLineFormBlock(
2083          item,
2084          'tel',
2085          self.options.userMetadata
2086        );
2087        break;
2088      case 'FileUpload':
2089        $fieldBlock = createFileFormBlock(item);
2090        break;
2091      case 'ParagraphText':
2092        $fieldBlock = createParagraphTextFormBlock(
2093          item,
2094          self.options.userMetadata
2095        );
2096        break;
2097      case 'MultipleChoice':
2098        $fieldBlock = createMultipleChoiceFormBlock(
2099          item,
2100          self.options.userMetadata
2101        );
2102        break;
2103      case 'Checkboxes':
2104        $fieldBlock = createCheckboxesFormBlock(
2105          item,
2106          self.options.userMetadata
2107        );
2108        break;
2109      case 'DropdownMenu':
2110        $fieldBlock = createDropdownMenuFormBlock(
2111          item,
2112          self.options.userMetadata
2113        );
2114        break;
2115      case 'ConsentCheckbox':
2116        $fieldBlock = createConsentCheckboxFormBlock(item);
2117        break;
2118      case 'SectionDivider':
2119        $fieldBlock = createSectionDividerFormBlock(item);
2120        break;
2121      case 'SingleLineZipCode':
2122        $fieldBlock = createSingleLineZipCode(item, self.options.userMetadata);
2123        break;
2124      case 'FieldPayment':
2125        $fieldBlock = createPaymentCart(item, self.options.userMetadata);
2126        //Remove row class to display shopping cart properly
2127        $container.closest('form').parent().removeClass('row');
2128        break;
2129    }
2130    if ($fieldBlock) {
2131      if (item.display == false) $fieldBlock.hide();
2132      $container.append($fieldBlock);
2133    }
2134  }
2135
2136  function displayLoadingStep(self, stepToDisplay, isLastStep) {
2137    if (self.formData.form.advancedOptions.stepTransition == null) {
2138      return false;
2139    }
2140
2141    //Don't display if going back a step
2142    if (self.$loadingIndicatorContainer.hasClass('goback')) {
2143      self.$loadingIndicatorContainer.removeClass('goback');
2144      return false;
2145    }
2146
2147    //Don't display custom loading message if refreshed page or revisiting page
2148    if (self.$loadingIndicatorContainer.hasClass('firstload')) {
2149      self.$loadingIndicatorContainer.removeClass('firstload');
2150      return false;
2151    }
2152
2153    //Don't load on the first step
2154    if (stepToDisplay <= 1) {
2155      return false;
2156    }
2157
2158    var loadingMessage =
2159      self.formData.form.advancedOptions.stepTransition[stepToDisplay - 1];
2160    if (!loadingMessage) {
2161      return false;
2162    }
2163
2164    //Hide the form contents
2165    var formId = BBMForms.constants.formNamePrefix + self.element.id;
2166    bbmFormsJQ('#' + formId).css('display', 'none');
2167
2168    var $loadingMessageBlock = bbmFormsJQ('<p/>').text(loadingMessage);
2169    var $loadingImageBlock = bbmFormsJQ('<div/>')
2170      .addClass('loading-gif')
2171      .append('<img src=' + BBMForms.constants.loadingImagePath + ' />');
2172    self.$loadingIndicatorContainer.html($loadingMessageBlock);
2173    self.$loadingIndicatorContainer.append($loadingImageBlock);
2174
2175    var transitionTime = BBMForms.defaultOptions.loadingTime;
2176    if (
2177      self.formData.form.advancedOptions.stepTransitionTime != null &&
2178      self.formData.form.advancedOptions.stepTransitionTime > 0
2179    )
2180      transitionTime =
2181        self.formData.form.advancedOptions.stepTransitionTime * 1000; //convert to ms
2182
2183    let hasReturnUrl =
2184      self.options.returnUrl ||
2185      self.formData.form.advancedOptions.confirmationActionURL;
2186    if (isLastStep && hasReturnUrl) {
2187      let returnUrl = self.options.returnUrl
2188        ? self.options.returnUrl
2189        : self.formData.form.advancedOptions.confirmationActionURL;
2190      setTimeout(
2191        returnFromLoadingStep.bind(null, self, returnUrl),
2192        transitionTime
2193      );
2194    } else {
2195      setTimeout(hideLoadingStep.bind(null, self), transitionTime);
2196    }
2197
2198    return true;
2199  }
2200
2201  function hideLoadingStep(self) {
2202    self.$loadingIndicatorContainer.empty();
2203    var formId = BBMForms.constants.formNamePrefix + self.element.id;
2204    bbmFormsJQ('#' + formId).css('display', '');
2205  }
2206
2207  function returnFromLoadingStep(self, returnUrl) {
2208    if (returnUrl) {
2209      if (self.options.callbackPostPassThrough) {
2210        let leadRecordsCopy = null;
2211        if (
2212          self.formData.hasOwnProperty('leadRecords') &&
2213          typeof self.formData.leadRecords != 'undefined'
2214        ) {
2215          leadRecordsCopy = Object.assign({}, self.formData.leadRecords);
2216        }
2217        //let leadRecordsFullCopy = null;
2218        //if (self.formData.hasOwnProperty('leadRecordsFull') && typeof self.formData.leadRecordsFull != "undefined") {
2219        //    leadRecordsFullCopy = Object.assign({}, self.formData.leadRecordsFull);
2220        //}
2221        let formDataCopy = Object.assign({}, self.formData);
2222        //leadrecords are being returned as separate param so remove from formData which is in the same return params set
2223        if (formDataCopy.hasOwnProperty('leadRecords'))
2224          delete formDataCopy.leadRecords;
2225        if (formDataCopy.hasOwnProperty('leadRecordsFull'))
2226          delete formDataCopy.leadRecordsFull;
2227
2228        self.options.callbackPostPassThrough(
2229          leadRecordsCopy,
2230          null,
2231          null,
2232          formDataCopy
2233        );
2234      }
2235
2236      window.location = returnUrl;
2237    }
2238  }
2239
2240  function displayStep(self, stepToDisplay, errMsg) {
2241    self.$progressIndicatorContainer.html(
2242      createProgressIndicator(
2243        self.formData.form.advancedOptions.showProgress,
2244        stepToDisplay,
2245        self.formData.form.steps.length
2246      )
2247    );
2248
2249    //Display validation error messages
2250    self.$errorMessage.empty().hide();
2251    var displayError = errMsg != undefined;
2252    if (errMsg) {
2253      self.$errorMessage.append(bbmFormsJQ('<p/>').text(errMsg)).show();
2254    }
2255    if (self.formData.form.validationError) {
2256      var msgHtml = decodeHtml(self.formData.form.validationError);
2257      self.$errorMessage.append(msgHtml).show();
2258      displayError = true;
2259    }
2260
2261    //If no error, display loading step
2262    if (!displayError) displayLoadingStep(self, stepToDisplay);
2263
2264    //Empty out all steps.
2265    self.$stepsWrapper.empty();
2266
2267    //Create new step container
2268    var stepContainerId = self.$form.attr('id') + '_' + stepToDisplay;
2269    self.$currentStepContainer = bbmFormsJQ('<div/>', {
2270      id: stepContainerId,
2271    }).addClass(BBMForms.constants.stepContainerCssClass);
2272    self.$currentStepContainer.append(
2273      bbmFormsJQ('<input/>', { type: 'hidden', name: 'stepToDisplay' }).val(
2274        stepToDisplay
2275      )
2276    );
2277    self.$stepsWrapper.append(self.$currentStepContainer);
2278
2279    //If this is a multi-step form with more steps to go, show the continue button.
2280    var isLastStep = stepToDisplay == self.formData.form.steps.length;
2281
2282    var $buttonsContainer = bbmFormsJQ('<div/>')
2283      .addClass(BBMForms.constants.buttonsContainerCssClass)
2284      .addClass(getBootstrapColumnClasses('full'));
2285    var nextBtnTxt = isLastStep
2286      ? self.formData.form.advancedOptions.actionButtonText
2287        ? self.formData.form.advancedOptions.actionButtonText
2288        : BBMForms.constants.submitButtonTextDefault
2289      : BBMForms.constants.nextStepButtonText;
2290    var nextBtnColor = self.formData.form.advancedOptions.buttonColor
2291      ? self.formData.form.advancedOptions.buttonColor
2292      : BBMForms.constants.submitButtonColorDefault;
2293    var $nextBtn = bbmFormsJQ('<button/>')
2294      .text(nextBtnTxt)
2295      .attr('title', nextBtnTxt)
2296      .addClass(BBMForms.constants.nextButtonCssClass)
2297      .attr('style', 'background-color:#'.concat(nextBtnColor, ';'));
2298
2299    if (self.options.enableAutoProgress == true) {
2300      self.options.$nextBtn = $nextBtn;
2301    }
2302
2303    $nextBtn.click(function (e) {
2304      e.preventDefault();
2305
2306      //Disable button to prevent multiple clicks.
2307      var $allBtns = $buttonsContainer.find('button').prop('disabled', true);
2308      var $btn = bbmFormsJQ(this).text('Processing...');
2309      continueForm(self);
2310    });
2311
2312    var stepIndex = stepToDisplay - 1;
2313    var isPaymentStep = false;
2314    var paymentFieldId = 0;
2315    bbmFormsJQ.each(
2316      self.formData.form.steps[stepIndex],
2317      function (index, item) {
2318        //If a step is a single field that only accepts a single selection answer, enable auto-progress
2319        if (
2320          self.options.enableAutoProgress == true &&
2321          self.formData.form.steps[stepIndex].length == 1 &&
2322          !isLastStep &&
2323          (self.formData.form.steps[stepIndex][0].fieldType ==
2324            'MultipleChoice' ||
2325            self.formData.form.steps[stepIndex][0].fieldType == 'DropdownMenu')
2326        ) {
2327          //assign auto progression properties
2328          item.enableAutoProgress = true;
2329          item.$enableAutoProgressBtn = self.options.$nextBtn;
2330        }
2331        if (item.fieldType == 'FileUpload') {
2332          item.buttonColor = nextBtnColor;
2333        }
2334        addField(self, item);
2335
2336        if (item.fieldType == 'FieldPayment') {
2337          isPaymentStep = true;
2338          paymentFieldId = item.externalId;
2339        }
2340      }
2341    );
2342
2343    //Conditional Display
2344    BBMForms.conditionalDisplayChangeEvents = [];
2345    bbmFormsJQ.each(
2346      self.formData.form.steps[stepIndex],
2347      function (index, item) {
2348        configureConditionalDisplay(self, item);
2349      }
2350    );
2351
2352    bbmFormsJQ.each(
2353      self.formData.form.steps[stepIndex],
2354      function (index, item) {
2355        prePopulateAndValidate(self, item);
2356      }
2357    );
2358
2359    //Append the Consent Text before submit button
2360    if (
2361      (isLastStep || self.formData.form.isConditionalForm) &&
2362      self.formData.form.consentLevelText
2363    ) {
2364      var consentHtml = decodeHtml(self.formData.form.consentLevelText);
2365      var $consentLevel = bbmFormsJQ('<div/>')
2366        .addClass(BBMForms.constants.disclaimerCssClass)
2367        .addClass(getBootstrapColumnClasses('full'))
2368        .html(consentHtml);
2369      self.$currentStepContainer.append($consentLevel);
2370    }
2371
2372    //Append buttons container after the steps fields have been added
2373    self.$currentStepContainer.append($buttonsContainer);
2374
2375    //Hide submit button for payment step but allow user to go back and update stuff
2376    //the CHECKOUT button should replace this submit button
2377    if (!isPaymentStep) {
2378      $buttonsContainer.append($nextBtn);
2379    } else {
2380      var $checkoutBtn = bbmFormsJQ('<button/>')
2381        .addClass('btn btn-primary btn-block next-button mt-4 checkoutBtn')
2382        .text(BBMForms.constants.paymentCheckoutBtnDisableText)
2383        .attr('title', BBMForms.constants.paymentCheckoutBtnText);
2384      $checkoutBtn.prop('disabled', true); //disable button unless there is at least one checkbox checked off
2385      var $mobileCheckoutBtn = bbmFormsJQ('<button/>')
2386        .addClass('next-button btn btn-primary btn-primary-large checkoutBtn')
2387        .text(BBMForms.constants.paymentCheckoutBtnDisableText)
2388        .attr('title', BBMForms.constants.paymentCheckoutBtnText);
2389      $mobileCheckoutBtn.prop('disabled', true);
2390
2391      $checkoutBtn.click(function (e) {
2392        e.preventDefault();
2393
2394        //Disable button to prevent multiple clicks.
2395        $buttonsContainer.find('button').prop('disabled', true);
2396        bbmFormsJQ(this).text('Processing...');
2397        continueToPayments(self, paymentFieldId);
2398      });
2399      $mobileCheckoutBtn.click(function (e) {
2400        e.preventDefault();
2401
2402        //Disable button to prevent multiple clicks.
2403        $buttonsContainer.find('button').prop('disabled', true);
2404        bbmFormsJQ(this).text('Processing...');
2405        continueToPayments(self, paymentFieldId);
2406      });
2407      var $cartContainer = bbmFormsJQ('.cart-desktop');
2408      $cartContainer.append($checkoutBtn);
2409      bbmFormsJQ('.cart-mobile').append($mobileCheckoutBtn);
2410    }
2411
2412    var canGoBack = stepToDisplay > 1;
2413    if (canGoBack) {
2414      //Back button to show to the right so it can drop to next line on small width
2415      var backBtnTxt = BBMForms.constants.previousStepButtonText;
2416      var $backBtn = bbmFormsJQ('<button/>')
2417        .text(backBtnTxt)
2418        .addClass(BBMForms.constants.previousButtonCssClass)
2419        .data('text', backBtnTxt)
2420        .click(function (e) {
2421          e.preventDefault();
2422          //Disable button to prevent multiple clicks.
2423          var $allBtns = $buttonsContainer
2424            .find('button')
2425            .prop('disabled', true);
2426          var $btn = bbmFormsJQ(this).text('Processing...');
2427          backToStep(self, stepToDisplay - 1);
2428        });
2429      $buttonsContainer.append($backBtn);
2430    }
2431
2432    if (isLastStep && self.formData.form.advancedOptions.disclaimer) {
2433      //wysiwyg
2434      var disclaimerHtml = decodeHtml(
2435        self.formData.form.advancedOptions.disclaimer
2436      );
2437      var $disclaimer = bbmFormsJQ('<div/>')
2438        .addClass(BBMForms.constants.disclaimerCssClass)
2439        .addClass(getBootstrapColumnClasses('full'))
2440        .html(disclaimerHtml);
2441      self.$currentStepContainer.append($disclaimer);
2442    }
2443  }
2444
2445  function updateFieldClientProperties(self) {
2446    bbmFormsJQ.each(self.formData.form.steps, function (index, step) {
2447      bbmFormsJQ.each(step, function (index, item) {
2448        //set clientId
2449        var fieldId =
2450          BBMForms.constants.fieldIdPrefix + self.element.id + item.externalId;
2451        //Create client-only properties to keep information needed for client use only. This will be deleted when posting.
2452        item.clientProperties = item.clientProperties || {};
2453        //Create clientId to access on submit
2454        item.clientProperties.clientId = fieldId;
2455      });
2456    });
2457  }
2458
2459  function updateFormDataFromInput(self) {
2460    //Go through form fields and update formData field values.
2461    var stepIndex = self.formData.stepToDisplay - 1;
2462    bbmFormsJQ.each(
2463      self.formData.form.steps[stepIndex],
2464      function (index, item) {
2465        updateFormDataFromInputForSingleField(item);
2466      }
2467    );
2468  }
2469
2470  function updateFormDataFromInputForSingleField(item) {
2471    switch (item.fieldType) {
2472      case 'SingleLineText':
2473      case 'SingleLineEmailAddress':
2474      case 'SingleLineURL':
2475      case 'SingleLineDate':
2476      case 'SingleLineNumber':
2477      case 'ParagraphText':
2478        updateFormDataForSingleValue(item);
2479        break;
2480      case 'FileUpload':
2481        updateFormDataForFile(item);
2482        break;
2483      case 'DropdownMenu':
2484        updateFormDataForDropDown(item);
2485        break;
2486      case 'MultipleChoice':
2487        updateFormDataForRadio(item);
2488        break;
2489      case 'Checkboxes':
2490        updateFormDataForCheckBox(item);
2491        break;
2492      case 'ConsentCheckbox':
2493        updateFormDataForConsentCheckBox(item);
2494        break;
2495    }
2496  }
2497
2498  function createFormData(self) {
2499    //set/update metadata before submiting the form.
2500    setMetaData(self);
2501
2502    //Clean up other fields.
2503
2504    //Do not send the empty dropdown value for the first option "- select -" if Other input field is filled.
2505    var selectToRemove = [];
2506    self.$form
2507      .find(
2508        "input:enabled[id$='" + BBMForms.constants.otherFieldIdSuffix + "']"
2509      )
2510      .closest('.' + BBMForms.constants.fieldBlockCssClass)
2511      .find('select')
2512      .each(function () {
2513        if (
2514          bbmFormsJQ(this).find('option:selected').length == 1 &&
2515          bbmFormsJQ(this).find('option:selected')[0].value == ''
2516        ) {
2517          selectToRemove.push(bbmFormsJQ(this));
2518        }
2519      });
2520    bbmFormsJQ.each(selectToRemove, function (index, $item) {
2521      $item.remove();
2522    });
2523    //If "Other" is selected/checked, the other input field value is sent regardless of it being empty or not.
2524
2525    //Remove hidden checkbox field if there are values selected so the empty value does not get sent with the actual values
2526    //Note!! This should be performed before below where we do the removal of the Other checkbox if other input field is shown.
2527    self.$form
2528      .find(
2529        "input[id$='" +
2530          '_hidden' +
2531          BBMForms.constants.multipleCheckboxNameSuffix +
2532          "']"
2533      )
2534      .each(function () {
2535        if (
2536          self.$form.find("input[name$='" + this.name + "']:checked").length > 0
2537        )
2538          this.parentElement.removeChild(this); //workaround for IE11
2539      });
2540
2541    //Remove hidden radiobutton field if there are values selected so the empty value does not get sent with the actual values
2542    //Note!! This should be performed before below where we do the removal of the Other radiobutton if other input field is shown.
2543    self.$form
2544      .find(
2545        "input[id$='" +
2546          '_hidden' +
2547          BBMForms.constants.multipleRadioButtonNameSuffix +
2548          "']"
2549      )
2550      .each(function () {
2551        if (
2552          self.$form.find("input[name$='" + this.name + "']:checked").length > 0
2553        )
2554          this.parentElement.removeChild(this); //workaround for IE11
2555      });
2556
2557    //Do not send the "Other" dropdown/checkbox/radio value if Other input field is shown.
2558    self.$form
2559      .find(
2560        "input:enabled[id$='" + BBMForms.constants.otherFieldIdSuffix + "']"
2561      )
2562      .closest('.' + BBMForms.constants.fieldBlockCssClass)
2563      .find('select')
2564      .remove();
2565    self.$form
2566      .find(
2567        "input:enabled[id$='" + BBMForms.constants.otherFieldIdSuffix + "']"
2568      )
2569      .closest('.' + BBMForms.constants.fieldBlockCssClass)
2570      .find(
2571        "input[type='checkbox'][value='" + BBMForms.constants.otherText + "']"
2572      )
2573      .remove();
2574    self.$form
2575      .find(
2576        "input:enabled[id$='" + BBMForms.constants.otherFieldIdSuffix + "']"
2577      )
2578      .closest('.' + BBMForms.constants.fieldBlockCssClass)
2579      .find("input[type='radio'][value='" + BBMForms.constants.otherText + "']")
2580      .remove();
2581
2582    //If "Other" is not selected/checked, remove the other input field so the empty value does not get sent.
2583    self.$form
2584      .find(
2585        "input:disabled[id$='" + BBMForms.constants.otherFieldIdSuffix + "']"
2586      )
2587      .remove();
2588
2589    //Remove consent checkboxes. Hidden field has the actual value.
2590    self.$form
2591      .find(
2592        "input[name$='" + BBMForms.constants.consentCheckboxNameSuffix + "']"
2593      )
2594      .remove();
2595
2596    //Remove display false fields except consent to prevent causing required/validation errors on the server.
2597    //ConsentCheckbox is an exception and is not removed because we need its data (the fact that it was not displayed to the user with empty value) stored on the server.
2598    var stepIndex = self.formData.stepToDisplay - 1;
2599    bbmFormsJQ.each(
2600      self.formData.form.steps[stepIndex],
2601      function (index, item) {
2602        if (item.fieldType != 'ConsentCheckbox' && item.display == false) {
2603          self.$form.find("[name='" + item.externalId + "']").remove();
2604        }
2605      }
2606    );
2607
2608    return self.$form;
2609  }
2610
2611  function continueForm(self) {
2612    //Empty out and hide error message
2613    self.$errorMessage.empty().hide();
2614
2615    //Update formData object with user input data
2616    updateFormDataFromInput(self);
2617
2618    //Post this.formData to api endpoint and validate
2619    var $fData = createFormData(self);
2620
2621    if (
2622      window.FormData &&
2623      navigator.userAgent.indexOf('MSIE') < 0 &&
2624      navigator.appVersion.indexOf('Trident/') < 0
2625    ) {
2626      var fData = new FormData($fData[0]);
2627      postFormData(
2628        fData,
2629        function (data) {
2630          var stepToDisplay = self.formData.stepToDisplay + 1;
2631          self.formData = data;
2632
2633          updateFieldClientProperties(self);
2634
2635          var displayLoading = displayLoadingStep(self, stepToDisplay, true);
2636
2637          //if returnUrl exist, need to defer the return for the loading transition to complete
2638          let hasReturnUrl =
2639            self.options.returnUrl ||
2640            self.formData.form.advancedOptions.confirmationActionURL;
2641          var deferReturn = hasReturnUrl && displayLoading;
2642
2643          //Pass through. We have a completed lead already.
2644          handleCompletion(self, deferReturn);
2645        },
2646        function (data) {
2647          self.formData = data;
2648
2649          updateFieldClientProperties(self);
2650
2651          //Incomplete lead. Show form.
2652          displayStep(self, self.formData.stepToDisplay);
2653
2654          if (self.options.callbackNextStepLoaded) {
2655            self.options.callbackNextStepLoaded(self.formData.stepToDisplay);
2656          }
2657
2658          scrollUpToTop(self);
2659        },
2660        function (errorMessage) {
2661          //Failed to post.
2662          displayStep(self, self.formData.stepToDisplay);
2663
2664          if (self.options.callbackPostError) {
2665            self.options.callbackPostError(self.formData.stepToDisplay);
2666          }
2667
2668          self.$errorMessage.html(bbmFormsJQ('<p/>').text(errorMessage)).show();
2669          scrollUpToTop(self);
2670        }
2671      );
2672    } else {
2673      var fData = $fData.serialize();
2674      postSerializedForm(
2675        fData,
2676        function (data) {
2677          var stepToDisplay = self.formData.stepToDisplay + 1;
2678          self.formData = data;
2679
2680          updateFieldClientProperties(self);
2681
2682          var displayLoading = displayLoadingStep(self, stepToDisplay, true);
2683
2684          //if returnUrl exist, need to defer the return for the loading transition to complete
2685          let hasReturnUrl =
2686            self.options.returnUrl ||
2687            self.formData.form.advancedOptions.confirmationActionURL;
2688          var deferReturn = hasReturnUrl && displayLoading;
2689
2690          //Pass through. We have a completed lead already.
2691          handleCompletion(self, deferReturn);
2692        },
2693        function (data) {
2694          self.formData = data;
2695
2696          updateFieldClientProperties(self);
2697
2698          //Incomplete lead. Show form.
2699          displayStep(self, self.formData.stepToDisplay);
2700
2701          if (self.options.callbackNextStepLoaded) {
2702            self.options.callbackNextStepLoaded(self.formData.stepToDisplay);
2703          }
2704
2705          scrollUpToTop(self);
2706        },
2707        function (errorMessage) {
2708          //Failed to post.
2709          displayStep(self, self.formData.stepToDisplay);
2710
2711          if (self.options.callbackPostError) {
2712            self.options.callbackPostError(self.formData.stepToDisplay);
2713          }
2714
2715          self.$errorMessage.html(bbmFormsJQ('<p/>').text(errorMessage)).show();
2716          scrollUpToTop(self);
2717        }
2718      );
2719    }
2720  }
2721
2722  function continueToPayments(self, paymentExternalFieldId) {
2723    //Empty out and hide error message
2724    self.$errorMessage.empty().hide();
2725
2726    //Update formData object with user input data
2727    updateFormDataFromInput(self);
2728
2729    //Post this.formData to api endpoint and validate
2730    var $fData = createFormData(self);
2731
2732    //Posted information
2733    if (
2734      window.FormData &&
2735      navigator.userAgent.indexOf('MSIE') < 0 &&
2736      navigator.appVersion.indexOf('Trident/') < 0
2737    ) {
2738      var fData = new FormData($fData[0]);
2739      postFormData(
2740        fData,
2741        function (data) {
2742          var stepToDisplay = self.formData.stepToDisplay + 1;
2743          self.formData = data;
2744
2745          updateFieldClientProperties(self);
2746
2747          var displayLoading = displayLoadingStep(self, stepToDisplay, true);
2748
2749          //if returnUrl exist, need to defer the return for the loading transition to complete
2750          let hasReturnUrl =
2751            self.options.returnUrl ||
2752            self.formData.form.advancedOptions.confirmationActionURL;
2753          var deferReturn = hasReturnUrl && displayLoading;
2754
2755          //Pass through. We have a completed lead already.
2756          handleCompletion(self, deferReturn);
2757        },
2758        function (data) {
2759          var params = {
2760            formId: data.form.externalId,
2761            sessionId: data.sessionId,
2762            userSsoId: data.userSsoId,
2763            returnUrl: encodeURIComponent(window.location.href),
2764          };
2765          window.location =
2766            BBMForms.constants.secureCheckoutUrl +
2767            '?' +
2768            bbmFormsJQ.param(params);
2769        },
2770        function (errorMessage) {
2771          displayStep(self, self.formData.stepToDisplay);
2772          self.$errorMessage.html(bbmFormsJQ('<p/>').text(errorMessage)).show();
2773          scrollUpToTop(self);
2774        }
2775      );
2776    } else {
2777      var fData = $fData.serialize();
2778      postSerializedForm(
2779        fData,
2780        function (data) {
2781          var stepToDisplay = self.formData.stepToDisplay + 1;
2782          self.formData = data;
2783
2784          updateFieldClientProperties(self);
2785
2786          var displayLoading = displayLoadingStep(self, stepToDisplay, true);
2787
2788          let hasReturnUrl =
2789            self.options.returnUrl ||
2790            self.formData.form.advancedOptions.confirmationActionURL;
2791          var deferReturn = hasReturnUrl && displayLoading;
2792
2793          handleCompletion(self, deferReturn);
2794        },
2795        function (data) {
2796          var params = {
2797            redirectUrl: window.location.href,
2798            userUID: data.userId,
2799            formID: data.form.externalId,
2800            sessionID: data.sessionId,
2801            fieldID: paymentExternalFieldId,
2802          };
2803          window.location =
2804            BBMForms.constants.secureCheckoutUrl +
2805            '?' +
2806            bbmFormsJQ.param(params);
2807        },
2808        function (errorMessage) {
2809          displayStep(self, self.formData.stepToDisplay);
2810          self.$errorMessage.html(bbmFormsJQ('<p/>').text(errorMessage)).show();
2811          scrollUpToTop(self);
2812        }
2813      );
2814    }
2815  }
2816
2817  function bypassForm(self) {
2818    //Update formData object with user input data
2819    updateFormDataFromInput(self);
2820
2821    //Post this.formData to api endpoint and validate
2822    var $fData = createFormData(self);
2823
2824    if (window.FormData) {
2825      var fData = new FormData($fData[0]);
2826      postBypassFormData(
2827        fData,
2828        function (data) {
2829          self.formData = data;
2830
2831          updateFieldClientProperties(self);
2832
2833          //Store userId in cookie. Update with userId retrieved from previously completed form.
2834          setCookie(
2835            BBMForms.constants.userIdCookieName,
2836            self.formData.userId,
2837            self.options.userIdCookieExpireDays,
2838            self.options.hostName
2839          );
2840
2841          //Pass through. We have a completed lead already.
2842          handleCompletion(self);
2843        },
2844        function (errorMessage) {
2845          //Bypass failed. Display bypass again.
2846          displayBypass(self, errorMessage);
2847          scrollUpToTop(self);
2848        },
2849        function (errorMessage) {
2850          //Bypass failed due to missing form/lead. Display form.
2851          self.display(errorMessage);
2852          scrollUpToTop(self);
2853        },
2854        function (errorMessage) {
2855          //Failed to post.
2856          displayBypass(self, errorMessage);
2857          scrollUpToTop(self);
2858        }
2859      );
2860    } else {
2861      var fData = $fData.serialize();
2862      postBypassSerializedForm(
2863        fData,
2864        function (data) {
2865          self.formData = data;
2866
2867          updateFieldClientProperties(self);
2868
2869          //Pass through. We have a completed lead already.
2870          handleCompletion(self);
2871        },
2872        function (errorMessage) {
2873          //Bypass failed. Display bypass again.
2874          displayBypass(self, errorMessage);
2875          scrollUpToTop(self);
2876        },
2877        function (errorMessage) {
2878          //Bypass failed due to missing form/lead. Display form.
2879          self.display(errorMessage);
2880          scrollUpToTop(self);
2881        },
2882        function (errorMessage) {
2883          //Failed to post.
2884          displayBypass(self, errorMessage);
2885          scrollUpToTop(self);
2886        }
2887      );
2888    }
2889  }
2890
2891  function backToStep(self, stepToDisplay) {
2892    //Update formData object with user input data
2893    updateFormDataFromInput(self);
2894
2895    //check if step back is empty
2896    // Conditional rules may return display false fields
2897    for (var i = stepToDisplay - 1; i >= 0; i--) {
2898      if (
2899        self.formData.form.steps[i].filter(o => o.display == true).length > 0
2900      ) {
2901        stepToDisplay = i + 1;
2902        break;
2903      }
2904    }
2905    self.formData.stepToDisplay = stepToDisplay;
2906
2907    //assign a class to indicating its going back to skip loading screen
2908    if (stepToDisplay > 1) self.$loadingIndicatorContainer.addClass('goback');
2909
2910    //No posting. No validation. Just go back.
2911    displayStep(self, stepToDisplay);
2912
2913    if (self.options.callbackPrevStepLoaded) {
2914      self.options.callbackPrevStepLoaded(stepToDisplay);
2915    }
2916
2917    scrollUpToTop(self);
2918  }
2919
2920  function handleCompletion(self, deferReturn) {
2921    deactivateConfirmExit();
2922
2923    let leadRecordsCopy = null;
2924    if (
2925      self.formData.hasOwnProperty('leadRecords') &&
2926      typeof self.formData.leadRecords != 'undefined'
2927    ) {
2928      leadRecordsCopy = Object.assign({}, self.formData.leadRecords);
2929    }
2930    let leadRecordsFullCopy = null;
2931    if (
2932      self.formData.hasOwnProperty('leadRecordsFull') &&
2933      typeof self.formData.leadRecordsFull != 'undefined'
2934    ) {
2935      leadRecordsFullCopy = Object.assign({}, self.formData.leadRecordsFull);
2936    }
2937    let formDataCopy = Object.assign({}, self.formData);
2938    //leadrecords are being returned as separate param so remove from formData which is in the same return params set
2939    if (formDataCopy.hasOwnProperty('leadRecords'))
2940      delete formDataCopy.leadRecords;
2941    if (formDataCopy.hasOwnProperty('leadRecordsFull'))
2942      delete formDataCopy.leadRecordsFull;
2943
2944    //window.onSubmitBBMForms
2945    var onSubmitFunc = getOnSubmitFunc(self);
2946
2947    //if deferReturn=true, then skip the return step (this is only for loading transition cases)
2948    let hasReturnUrl =
2949      self.options.returnUrl ||
2950      self.formData.form.advancedOptions.confirmationActionURL;
2951    if (hasReturnUrl && !deferReturn) {
2952      if (self.options.callbackPostPassThrough) {
2953        self.options.callbackPostPassThrough(
2954          leadRecordsCopy,
2955          null,
2956          leadRecordsFullCopy,
2957          formDataCopy
2958        );
2959      }
2960
2961      let returnUrl = self.options.returnUrl
2962        ? self.options.returnUrl
2963        : self.formData.form.advancedOptions.confirmationActionURL;
2964      if (onSubmitFunc) {
2965        try {
2966          onSubmitFunc(leadRecordsCopy, formDataCopy.form, function () {
2967            window.location = returnUrl;
2968          });
2969        } catch {
2970          window.location = returnUrl;
2971        }
2972      } else {
2973        window.location = returnUrl;
2974      }
2975    } else {
2976      if (self.options.callbackNextStepLoaded) {
2977        self.options.callbackNextStepLoaded('Completion Message');
2978      }
2979
2980      if (self.$formInstructions) {
2981        self.$formInstructions.empty();
2982      }
2983      if (self.$progressIndicatorContainer) {
2984        self.$progressIndicatorContainer.empty();
2985      }
2986
2987      var msgHtml = self.formData.form.advancedOptions.confirmationMessage
2988        ? decodeHtml(self.formData.form.advancedOptions.confirmationMessage)
2989        : bbmFormsJQ('<p/>').text(
2990            BBMForms.constants.confirmationMessageDefault
2991          );
2992      var msgBlock = bbmFormsJQ('<div/>')
2993        .addClass('col-xs-12 col-sm-12 col-md-12 padding-bottom-30')
2994        .addClass(BBMForms.constants.messageContainerCssClass)
2995        .append(msgHtml);
2996      self.$stepsWrapper.html(msgBlock);
2997
2998      if (self.options.scrollToTopUponCompletion) {
2999        scrollUpToTop(self);
3000      }
3001
3002      if (self.options.callbackPostPassThrough) {
3003        self.options.callbackPostPassThrough(
3004          leadRecordsCopy,
3005          null,
3006          leadRecordsFullCopy,
3007          formDataCopy
3008        );
3009      }
3010
3011      if (onSubmitFunc) {
3012        onSubmitFunc(leadRecordsCopy, formDataCopy.form);
3013      }
3014    }
3015  }
3016
3017  function handlePreviouslyCompleted(self, leadExist) {
3018    deactivateConfirmExit();
3019    self.$element.empty();
3020    //leadExist, in combination with Cookie'd Consent (Revisit Bypass) and force input for payment field to show completed form
3021    var isRevisit =
3022      leadExist ||
3023      (self.formData.leadFormExternalId &&
3024        self.formData.leadFormExternalId === self.formData.form.externalId);
3025
3026    var stepType = "Cookie'd Visit Consent";
3027    var visitType = isRevisit ? 'Revisit' : "Cookie'd Visit";
3028
3029    //check revisit bypass
3030    if (isRevisit && self.formData.form.cookieConsentRevisitBypass) {
3031      let leadRecordsCopy = null;
3032      if (
3033        self.formData.hasOwnProperty('leadRecords') &&
3034        typeof self.formData.leadRecords != 'undefined'
3035      ) {
3036        leadRecordsCopy = Object.assign({}, self.formData.leadRecords);
3037      }
3038      //let leadRecordsFullCopy = null;
3039      //if (self.formData.hasOwnProperty('leadRecordsFull') && typeof self.formData.leadRecordsFull != "undefined") {
3040      //    leadRecordsFullCopy = Object.assign({}, self.formData.leadRecordsFull);
3041      //}
3042      let formDataCopy = Object.assign({}, self.formData);
3043      //leadrecords are being returned as separate param so remove from formData which is in the same return params set
3044      if (formDataCopy.hasOwnProperty('leadRecords'))
3045        delete formDataCopy.leadRecords;
3046      if (formDataCopy.hasOwnProperty('leadRecordsFull'))
3047        delete formDataCopy.leadRecordsFull;
3048
3049      let hasReturnUrl =
3050        self.options.returnUrl ||
3051        self.formData.form.advancedOptions.confirmationActionURL;
3052      if (hasReturnUrl) {
3053        stepType = 'Revisit Redirect';
3054
3055        if (self.options.callbackFormLoaded) {
3056          self.options.callbackFormLoaded(stepType);
3057        }
3058
3059        if (self.options.callbackGetPassThrough) {
3060          self.options.callbackGetPassThrough(
3061            leadRecordsCopy,
3062            visitType,
3063            null,
3064            formDataCopy
3065          );
3066        }
3067
3068        let returnUrl = self.options.returnUrl
3069          ? self.options.returnUrl
3070          : self.formData.form.advancedOptions.confirmationActionURL;
3071        window.location = returnUrl;
3072      } else {
3073        //Pass through. We have a completed lead already. Show the download button.
3074        stepType = 'Completion Message';
3075
3076        if (self.options.callbackFormLoaded) {
3077          self.options.callbackFormLoaded(stepType);
3078        }
3079
3080        self.$element.empty();
3081        var msgHtml = self.formData.form.advancedOptions
3082          .confirmationRevisitMessage
3083          ? decodeHtml(
3084              self.formData.form.advancedOptions.confirmationRevisitMessage
3085            )
3086          : '';
3087        if (msgHtml != '') {
3088          var msgBlock = bbmFormsJQ('<div/>')
3089            .addClass('col-xs-12 col-sm-12 col-md-12 padding-bottom-30')
3090            .addClass(BBMForms.constants.messageContainerCssClass)
3091            .append(msgHtml);
3092          self.$element.append(msgBlock);
3093        }
3094
3095        if (self.options.callbackGetPassThrough) {
3096          self.options.callbackGetPassThrough(
3097            leadRecordsCopy,
3098            visitType,
3099            null,
3100            formDataCopy
3101          );
3102        }
3103      }
3104    } else {
3105      //window.onSubmitBBMForms
3106      var onSubmitFunc = getOnSubmitFunc(self);
3107
3108      if (self.options.callbackFormLoaded) {
3109        self.options.callbackFormLoaded(stepType);
3110      }
3111
3112      //Show cookie'd consent and button below.
3113      if (self.formData.form.cookieConsentText) {
3114        var consentHtml = decodeHtml(self.formData.form.cookieConsentText);
3115        var $consentBlock = bbmFormsJQ('<div/>')
3116          .addClass(BBMForms.constants.disclaimerCssClass)
3117          .addClass(getBootstrapColumnClasses('full'))
3118          .addClass('margin-bottom-10')
3119          .html(consentHtml);
3120        self.$element.append($consentBlock);
3121      }
3122
3123      var actionText = self.formData.form.advancedOptions.confirmationActionText
3124        ? self.formData.form.advancedOptions.confirmationActionText
3125        : BBMForms.constants.confirmationActionButtonTextDefault;
3126      var $buttonsContainer = bbmFormsJQ('<div/>')
3127        .addClass(BBMForms.constants.buttonsContainerCssClass)
3128        .addClass(getBootstrapColumnClasses('full'));
3129      var $downloadField = bbmFormsJQ('<div/>')
3130        .addClass('full')
3131        .addClass(BBMForms.constants.fieldBlockCssClass)
3132        .addClass(getBootstrapColumnClasses('full'));
3133      var nextBtnColor = self.formData.form.advancedOptions.buttonColor
3134        ? self.formData.form.advancedOptions.buttonColor
3135        : BBMForms.constants.submitButtonColorDefault;
3136
3137      var formMetaData = getMetaData(self);
3138
3139      var $button = bbmFormsJQ('<a/>')
3140        .attr('href', 'javascript:void(0)')
3141        .text(actionText)
3142        .attr('style', 'background-color:#'.concat(nextBtnColor, ';'))
3143        .addClass(BBMForms.constants.buttonCssClass)
3144        .addClass(BBMForms.constants.nextButtonCssClass)
3145        .click(function () {
3146          //On Click, we should make a resource request so the lead is generated.
3147          var reqData = {
3148            formId: self.options.formId,
3149            userId: self.userId,
3150            userSsoId: self.userSsoId,
3151            userSsoEmail: self.userSsoEmail,
3152            ip: self.options.userIpAddress,
3153            origin: self.options.origin,
3154            title: self.options.title,
3155            formmetadata: JSON.stringify(formMetaData),
3156          };
3157          postResource(
3158            reqData,
3159            function (data) {
3160              self.formData = data;
3161
3162              updateFieldClientProperties(self);
3163
3164              let leadRecordsCopy = null;
3165              if (
3166                self.formData.hasOwnProperty('leadRecords') &&
3167                typeof self.formData.leadRecords != 'undefined'
3168              ) {
3169                leadRecordsCopy = Object.assign({}, self.formData.leadRecords);
3170              }
3171              let leadRecordsFullCopy = null;
3172              if (
3173                self.formData.hasOwnProperty('leadRecordsFull') &&
3174                typeof self.formData.leadRecordsFull != 'undefined'
3175              ) {
3176                leadRecordsFullCopy = Object.assign(
3177                  {},
3178                  self.formData.leadRecordsFull
3179                );
3180              }
3181              let formDataCopy = Object.assign({}, self.formData);
3182              //leadrecords are being returned as separate param so remove from formData which is in the same return params set
3183              if (formDataCopy.hasOwnProperty('leadRecords'))
3184                delete formDataCopy.leadRecords;
3185              if (formDataCopy.hasOwnProperty('leadRecordsFull'))
3186                delete formDataCopy.leadRecordsFull;
3187
3188              let hasReturnUrl =
3189                self.options.returnUrl ||
3190                self.formData.form.advancedOptions.confirmationActionURL;
3191              if (hasReturnUrl) {
3192                if (self.options.callbackPostPassThrough) {
3193                  self.options.callbackPostPassThrough(
3194                    leadRecordsCopy,
3195                    visitType,
3196                    leadRecordsFullCopy,
3197                    formDataCopy
3198                  );
3199                }
3200
3201                let returnUrl = self.options.returnUrl
3202                  ? self.options.returnUrl
3203                  : self.formData.form.advancedOptions.confirmationActionURL;
3204                if (onSubmitFunc) {
3205                  try {
3206                    onSubmitFunc(
3207                      leadRecordsCopy,
3208                      formDataCopy.form,
3209                      function () {
3210                        window.location = returnUrl;
3211                      }
3212                    );
3213                  } catch {
3214                    window.location = returnUrl;
3215                  }
3216                } else {
3217                  window.location = returnUrl;
3218                }
3219              } else {
3220                //Pass through. We have a completed lead already. Show the download button.
3221                if (self.options.callbackNextStepLoaded) {
3222                  self.options.callbackNextStepLoaded('Completion Message');
3223                }
3224
3225                self.$element.empty();
3226                var msgHtml = self.formData.form.advancedOptions
3227                  .confirmationMessage
3228                  ? decodeHtml(
3229                      self.formData.form.advancedOptions.confirmationMessage
3230                    )
3231                  : bbmFormsJQ('<p/>').text(
3232                      BBMForms.constants.confirmationMessageDefault
3233                    );
3234                var msgBlock = bbmFormsJQ('<div/>')
3235                  .addClass('col-xs-12 col-sm-12 col-md-12 padding-bottom-30')
3236                  .addClass(BBMForms.constants.messageContainerCssClass)
3237                  .append(msgHtml);
3238                self.$element.append(msgBlock);
3239
3240                if (self.options.callbackPostPassThrough) {
3241                  self.options.callbackPostPassThrough(
3242                    leadRecordsCopy,
3243                    visitType,
3244                    leadRecordsFullCopy,
3245                    formDataCopy
3246                  );
3247                }
3248
3249                if (onSubmitFunc) {
3250                  onSubmitFunc(leadRecordsCopy, formDataCopy.form);
3251                }
3252              }
3253            },
3254            function (data) {
3255              self.formData = data;
3256
3257              updateFieldClientProperties(self);
3258
3259              //Incomplete lead. Show form.
3260              self.display();
3261            },
3262            function (errorMessage) {
3263              self.$element.html(bbmFormsJQ('<p/>').text(errorMessage));
3264            }
3265          );
3266        });
3267
3268      var $downloadColumn = bbmFormsJQ('<div/>')
3269        .addClass(BBMForms.constants.columnCssClass)
3270        .append($button);
3271      self.$element.append(
3272        $buttonsContainer.append($downloadField.append($downloadColumn))
3273      );
3274    }
3275  }
3276
3277  function getOnSubmitFunc(self) {
3278    window.onSubmitBBMForms =
3279      window.onSubmitBBMForms && Array.isArray(window.onSubmitBBMForms)
3280        ? window.onSubmitBBMForms
3281        : [];
3282    var onSubmitFunc = null;
3283    var onSubmitIndex = window.onSubmitBBMForms.findIndex(
3284      item =>
3285        item.hasOwnProperty('formId') &&
3286        item.formId === self.formData.form.externalId
3287    );
3288    if (onSubmitIndex !== -1) {
3289      var onSubmitItem = window.onSubmitBBMForms.splice(onSubmitIndex, 1)[0]; //get array item while removing it from array
3290      if (
3291        onSubmitItem &&
3292        onSubmitItem.hasOwnProperty('onSubmit') &&
3293        typeof onSubmitItem.onSubmit === 'function'
3294      ) {
3295        onSubmitFunc = onSubmitItem.onSubmit;
3296      }
3297    }
3298    return onSubmitFunc;
3299  }
3300
3301  function scrollUpToTop(self) {
3302    bbmFormsJQ('html, body').animate(
3303      {
3304        scrollTop: self.$element.offset().top,
3305      },
3306      500
3307    );
3308  }
3309
3310  function getUrlParameter(name) {
3311    let results = [];
3312    let searchParams = new URLSearchParams(location.search);
3313    for (let key of searchParams.keys()) {
3314      if (key.trim().toLowerCase() == name.trim().toLowerCase()) {
3315        results.push(
3316          decodeURIComponent(searchParams.get(key).replace(/\+/g, ' ').trim())
3317        );
3318      }
3319    }
3320    return results;
3321  }
3322
3323  function getUrlParameterByPrefix(prefix) {
3324    let results = [];
3325    let searchParams = new URLSearchParams(location.search);
3326    for (let key of searchParams.keys()) {
3327      if (key.trim().toLowerCase().startsWith(prefix.trim().toLowerCase())) {
3328        results.push(decodeURIComponent(key.trim().replace(/\+/g, ' ').trim()));
3329      }
3330    }
3331    return results;
3332  }
3333
3334  function activateConfirmExit(isInteracted) {
3335    window.onbeforeunload = function (e) {
3336      confirmExit(e, isInteracted);
3337    };
3338  }
3339
3340  function deactivateConfirmExit() {
3341    window.onbeforeunload = null;
3342  }
3343
3344  function confirmExit(e, isInteracted) {
3345    if (isInteracted) {
3346      e.preventDefault();
3347      e.returnValue = '';
3348    }
3349    //return true;
3350  }
3351
3352  function getMetaData(self) {
3353    //Metadata defined on the form from FormsCMS.
3354    self.formData.form.formMetadata = self.formData.form.formMetadata || {}; //Default to empty object if not defined.
3355
3356    //1.
3357    //Create metadata with FormsCMS defined metadata (self.formData.form.formMetadata) as base and append embed script defined metadata (self.options.formMetadata)
3358    //embed script defined metadata overwrite FormsCMS defined metadata if both have same metadata key.
3359    var formMetaData = bbmFormsJQ.extend(
3360      {},
3361      self.formData.form.formMetadata,
3362      self.options.formMetadata
3363    );
3364
3365    //2.
3366    //If 3rd party script defined metadata exists this gets appended to the above result being used as base.
3367    //3rd party script will overwrite the above result if same metadata key found.
3368    if (
3369      typeof window.BBMFormsMetaData !== 'undefined' &&
3370      bbmFormsJQ.isArray(window.BBMFormsMetaData)
3371    ) {
3372      bbmFormsJQ.each(window.BBMFormsMetaData, function (index, data) {
3373        if (typeof data === 'object') {
3374          formMetaData = bbmFormsJQ.extend({}, formMetaData, data);
3375        }
3376      });
3377    }
3378
3379    //3.
3380    //If URL metadata capture is enabled then append metadata from URL querystring.
3381    if (self.options.enableMetadataCapture) {
3382      //Check to see if already defined metadata keys are there in the URL querystring.
3383      //Overwrite existing metadata properties if same key found in URL querystring.
3384      bbmFormsJQ.each(formMetaData, function (key, value) {
3385        var result = getUrlParameter(key);
3386        if (result.length > 0) {
3387          formMetaData[key] = result[0];
3388        }
3389      });
3390
3391      //Append metadata from URL querystring from the defined prefixes and specific params.
3392      bbmFormsJQ.each(self.options.metadataParamList, function (i) {
3393        var key = self.options.metadataParamList[i];
3394        var result = getUrlParameter(key);
3395        if (result.length > 0) {
3396          formMetaData[key] = result[0];
3397        }
3398      });
3399    }
3400
3401    return formMetaData;
3402  }
3403
3404  function setMetaData(self) {
3405    var metaData = getMetaData(self);
3406    var metaDataFieldName = 'formMetadata';
3407    self.$form.find("input[name$='" + metaDataFieldName + "']").remove();
3408    self.$form.append(
3409      bbmFormsJQ('<input/>', { type: 'hidden', name: metaDataFieldName }).val(
3410        JSON.stringify(metaData)
3411      )
3412    );
3413  }
3414
3415  var onArrayPush = function (arr, callback) {
3416    arr.push = function (e) {
3417      Array.prototype.push.call(arr, e);
3418      callback(arr);
3419    };
3420  };
3421
3422  BBMForms.prototype.display = function (errMsg) {
3423    if (!this.options.element) {
3424      throw new Error('No element option passed to BBMForms.');
3425    }
3426
3427    this.element = this.options.element;
3428    this.$element = bbmFormsJQ(this.element);
3429    this.$element.empty(); //clear
3430
3431    //Create container
3432    var $row = bbmFormsJQ('<div/>');
3433    this.$element.append(
3434      bbmFormsJQ('<div/>')
3435        .addClass(BBMForms.constants.formContainerCssClass)
3436        .append(bbmFormsJQ('<div/>').addClass('container').append($row))
3437    );
3438    this.$element = $row;
3439
3440    //Show loading message while get form is in progress...
3441    this.$element.html("<p style='text-align:center;'>Loading...</p>");
3442
3443    //Add formId to array of displayed forms to be used for blueconic form capture
3444    window.displayedBBMForms = window.displayedBBMForms ?? [];
3445    if (window.displayedBBMForms.indexOf(this.options.formId) === -1) {
3446      window.displayedBBMForms.push(this.options.formId);
3447    }
3448
3449    var reqData = {
3450      formId: this.options.formId,
3451      userId: this.userId,
3452      userSsoId: this.userSsoId,
3453      userSsoEmail: this.userSsoEmail,
3454      getFullLeadRecordsBackOnSuccess:
3455        this.options.getFullLeadRecordsBackOnSuccess,
3456      allowPrefill: this.options.allowPrefill,
3457      ip: this.options.userIpAddress,
3458      origin: this.options.origin,
3459      title: this.options.title,
3460      formmetadata: JSON.stringify(this.options.formMetadata),
3461    };
3462    var self = this;
3463    getForm(
3464      reqData,
3465      function (data) {
3466        self.formData = data;
3467
3468        updateFieldClientProperties(self);
3469
3470        //Pass through. We have a completed lead already. Show the download button.
3471        handlePreviouslyCompleted(self);
3472      },
3473      function (data) {
3474        //Incomplete lead. Show form.
3475
3476        self.formData = data;
3477
3478        updateFieldClientProperties(self);
3479
3480        //leadCompleted in query string sent from success payment, to skip form data and show completed form message
3481        var sp = getUrlParameter('leadCompleted');
3482        if (sp == 'true') {
3483          handlePreviouslyCompleted(self, true);
3484          return;
3485        }
3486
3487        if (self.options.callbackFormLoading) {
3488          self.options.callbackFormLoading();
3489        }
3490
3491        //Store userId in cookie.
3492        setCookie(
3493          BBMForms.constants.userIdCookieName,
3494          self.formData.userId,
3495          self.options.userIdCookieExpireDays,
3496          self.options.hostName
3497        );
3498
3499        //Clean out loading message.
3500        self.$element.empty();
3501
3502        self.userId = self.formData.userId;
3503        self.userSsoId = self.formData.userSsoId;
3504        self.userSsoEmail = self.formData.userSsoEmail;
3505        self.sessionId = self.formData.sessionId;
3506        self.options.enableAutoProgress = self.formData.form.enableAutoProgress;
3507        self.options.enableConfirmExit = self.formData.form.enableConfirmExit;
3508
3509        //Header
3510        if (
3511          self.formData.form.advancedOptions.headline ||
3512          self.formData.form.advancedOptions.description
3513        ) {
3514          var $header = bbmFormsJQ('<header/>').addClass(
3515            'col-md-8 col-lg-9 col-center align-center padding-bottom-30'
3516          );
3517
3518          if (self.formData.form.advancedOptions.headline) {
3519            //text
3520            var $formHead = bbmFormsJQ('<h2/>').text(
3521              self.formData.form.advancedOptions.headline
3522            );
3523            $header.append($formHead);
3524          }
3525
3526          if (self.formData.form.advancedOptions.description) {
3527            //wysiwyg
3528            var descHtml = decodeHtml(
3529              self.formData.form.advancedOptions.description
3530            );
3531            var $formDesc = bbmFormsJQ('<div/>')
3532              .addClass('description')
3533              .html(descHtml);
3534            $header.append($formDesc);
3535          }
3536
3537          self.$element.append(
3538            bbmFormsJQ('<div/>')
3539              .addClass('row justify-content-center')
3540              .append($header)
3541          );
3542        }
3543
3544        if (self.formData.form.steps) {
3545          //Progress indicator
3546          self.$progressIndicatorContainer = bbmFormsJQ('<div/>').addClass(
3547            'row justify-content-center'
3548          );
3549          self.$element.append(self.$progressIndicatorContainer);
3550
3551          //Loading indicator
3552          self.$loadingIndicatorContainer = bbmFormsJQ('<div/>').addClass(
3553            'row justify-content-center loading firstload'
3554          );
3555          self.$element.append(self.$loadingIndicatorContainer);
3556
3557          //Error message container
3558          self.$errorMessage = bbmFormsJQ('<div/>')
3559            .addClass(BBMForms.constants.errorMsgCssClass)
3560            .addClass('padding-bottom-30')
3561            .hide();
3562          self.$element.append(
3563            bbmFormsJQ('<div/>')
3564              .addClass('row justify-content-center')
3565              .append(
3566                bbmFormsJQ('<div/>')
3567                  .addClass('col-xs-12 col-sm-12 col-md-12')
3568                  .append(self.$errorMessage)
3569              )
3570          );
3571
3572          //Create form element
3573          var formClientId =
3574            BBMForms.constants.formNamePrefix + self.element.id;
3575          self.$form = bbmFormsJQ('<form/>', {
3576            id: formClientId,
3577            name: formClientId,
3578            method: 'post',
3579            enctype: 'multipart/form-data',
3580          });
3581          self.$element.append(
3582            bbmFormsJQ('<div/>')
3583              .addClass('row justify-content-center')
3584              .append(self.$form)
3585          );
3586
3587          //Hidden fields
3588          self.$form.append(
3589            bbmFormsJQ('<input/>', { type: 'hidden', name: 'formId' }).val(
3590              self.options.formId
3591            )
3592          );
3593          self.$form.append(
3594            bbmFormsJQ('<input/>', { type: 'hidden', name: 'userId' }).val(
3595              self.userId
3596            )
3597          );
3598          self.$form.append(
3599            bbmFormsJQ('<input/>', { type: 'hidden', name: 'userSsoId' }).val(
3600              self.userSsoId
3601            )
3602          );
3603          self.$form.append(
3604            bbmFormsJQ('<input/>', {
3605              type: 'hidden',
3606              name: 'userSsoEmail',
3607            }).val(self.userSsoEmail)
3608          );
3609          self.$form.append(
3610            bbmFormsJQ('<input/>', {
3611              type: 'hidden',
3612              name: 'getFullLeadRecordsBackOnSuccess',
3613            }).val(self.options.getFullLeadRecordsBackOnSuccess)
3614          );
3615          self.$form.append(
3616            bbmFormsJQ('<input/>', { type: 'hidden', name: 'sessionId' }).val(
3617              self.sessionId
3618            )
3619          );
3620          self.$form.append(
3621            bbmFormsJQ('<input/>', { type: 'hidden', name: 'ip' }).val(
3622              self.options.userIpAddress
3623            )
3624          );
3625          self.$form.append(
3626            bbmFormsJQ('<input/>', { type: 'hidden', name: 'origin' }).val(
3627              self.options.origin
3628            )
3629          );
3630          self.$form.append(
3631            bbmFormsJQ('<input/>', { type: 'hidden', name: 'title' }).val(
3632              self.options.title
3633            )
3634          );
3635
3636          //Form instructions
3637          self.$formInstructions = bbmFormsJQ('<div/>').addClass(
3638            'col-xs-12 col-sm-12 col-md-12'
3639          );
3640          self.$form.append(self.$formInstructions);
3641
3642          //Form bypass
3643          if (
3644            self.formData.form.advancedOptions.formBypass &&
3645            self.formData.form.advancedOptions.formBypassLinkText
3646          ) {
3647            self.$formBypass = bbmFormsJQ('<p/>')
3648              .addClass('padding-bottom-10')
3649              .css('text-align', 'right')
3650              .append(
3651                bbmFormsJQ('<a/>')
3652                  .attr('href', 'javascript:void(0);')
3653                  .text(self.formData.form.advancedOptions.formBypassLinkText)
3654                  .click(function (e) {
3655                    e.preventDefault();
3656                    displayBypass(self);
3657                  })
3658              );
3659            self.$formInstructions.append(self.$formBypass);
3660          }
3661
3662          //Required note
3663          self.$formInstructions.append(
3664            bbmFormsJQ('<p/>')
3665              .addClass(BBMForms.constants.instructionTextCssClass)
3666              .addClass('padding-bottom-20')
3667              .css('text-align', 'right')
3668              .text('Required ')
3669              .append(
3670                bbmFormsJQ('<sup/>')
3671                  .addClass(BBMForms.constants.requiredTextCssClass)
3672                  .text(BBMForms.constants.requiredText)
3673              )
3674          );
3675
3676          //Steps wrapper
3677          self.$stepsWrapper = bbmFormsJQ('<div/>').addClass(
3678            BBMForms.constants.stepsWrapperCssClass
3679          );
3680          self.$form.append(self.$stepsWrapper);
3681
3682          //Display step
3683          displayStep(self, self.formData.stepToDisplay, errMsg);
3684
3685          if (self.options.callbackFormLoaded) {
3686            self.options.callbackFormLoaded(self.formData.stepToDisplay);
3687          }
3688        }
3689
3690        if (self.options.enableConfirmExit) {
3691          var form = document.getElementById(
3692            'bbmforms_' + data.form.externalId
3693          );
3694          //event listener added to very end. Earlier placements may result in form variable being undefined as the form may not yet be present on DOM
3695          form.addEventListener('change', function () {
3696            //when event listen detects input value changes on form, pass interacted as true.
3697            activateConfirmExit(true);
3698          });
3699
3700          //when form first gets created, pass in false because it has yet to be interacted with.
3701          activateConfirmExit(false);
3702        }
3703      },
3704      function (errorMessage) {
3705        self.$element.html(bbmFormsJQ('<p/>').text(errorMessage));
3706      }
3707    );
3708  };
3709
3710  BBMForms.prototype.redirect = function () {
3711    if (!this.options.formUrl) {
3712      throw new Error('No formUrl option passed to BBMForms.');
3713    }
3714
3715    var reqData = {
3716      formId: this.options.formId,
3717      userId: this.userId,
3718      userSsoId: this.userSsoId,
3719      userSsoEmail: this.userSsoEmail,
3720      ip: this.options.userIpAddress,
3721      origin: this.options.origin,
3722      title: this.options.title,
3723      formmetadata: JSON.stringify(this.options.formMetadata),
3724    };
3725    var self = this;
3726    //Should do post-resource since the redirect function is for getting access to the resource where it should create a lead.
3727    postResource(
3728      reqData,
3729      function (data) {
3730        self.formData = data;
3731
3732        updateFieldClientProperties(self);
3733
3734        let leadRecordsCopy = null;
3735        if (
3736          self.formData.hasOwnProperty('leadRecords') &&
3737          typeof self.formData.leadRecords != 'undefined'
3738        ) {
3739          leadRecordsCopy = Object.assign({}, self.formData.leadRecords);
3740        }
3741        //let leadRecordsFullCopy = null;
3742        //if (self.formData.hasOwnProperty('leadRecordsFull') && typeof self.formData.leadRecordsFull != "undefined") {
3743        //    leadRecordsFullCopy = Object.assign({}, self.formData.leadRecordsFull);
3744        //}
3745        let formDataCopy = Object.assign({}, self.formData);
3746        //leadrecords are being returned as separate param so remove from formData which is in the same return params set
3747        if (formDataCopy.hasOwnProperty('leadRecords'))
3748          delete formDataCopy.leadRecords;
3749        if (formDataCopy.hasOwnProperty('leadRecordsFull'))
3750          delete formDataCopy.leadRecordsFull;
3751
3752        //Pass through. We have a completed lead already.
3753        if (self.options.callbackGetPassThrough) {
3754          self.options.callbackGetPassThrough(
3755            leadRecordsCopy,
3756            null,
3757            null,
3758            formDataCopy
3759          );
3760        }
3761        window.location = self.options.returnUrl;
3762      },
3763      function (data) {
3764        //Incomplete lead. Back to the form page.
3765        window.location = self.options.formUrl;
3766      },
3767      function (errorMessage) {
3768        throw new Error(errorMessage);
3769      }
3770    );
3771  };
3772
3773  BBMForms.constants = {
3774    //apiPath: "http://localhost:54217/api",
3775    apiPath: 'https://leaddata.bobitstudios.com/v2/api',
3776    userIdCookieName: 'BBMForms_UserID', //keep this name as it's shared name on other sites
3777    formNamePrefix: 'bbmforms_form_',
3778    fieldIdPrefix: 'bbmforms_field_',
3779    otherFieldIdSuffix: '_other',
3780    errorMsgCssClass: 'errorMsg',
3781    errorFieldCssClass: 'error',
3782    instructionTextCssClass: 'instructionText',
3783    requiredTextCssClass: 'required',
3784    requiredText: '*',
3785    optionalTextCssClass: 'optional',
3786    optionalText: '(Optional)',
3787    columnCssClass: 'column',
3788    otherText: 'Other',
3789    otherExplanationText: 'if other, please specify:',
3790    stepText: 'Step',
3791    progressIndicatorCssClass: 'progressIndicator',
3792    progressCurrentStepCssClass: 'current',
3793    progressBarContainerCssClass: 'progress',
3794    progressBarCssClass: 'bar',
3795    stepsWrapperCssClass: 'steps_wrapper',
3796    stepContainerCssClass: 'step',
3797    textareaDefaultRows: 6,
3798    buttonsContainerCssClass: 'buttons',
3799    previousStepButtonText: 'Back',
3800    nextStepButtonText: 'Continue',
3801    submitButtonTextDefault: 'Submit',
3802    confirmationActionButtonTextDefault: 'Download',
3803    submitButtonColorDefault: '3C549E',
3804    confirmationMessageDefault: 'Thank you.',
3805    previousButtonCssClass: 'btn-secondary',
3806    buttonCssClass: 'button',
3807    nextButtonCssClass: 'btn-primary',
3808    submitButtonCssClass: 'btn-primary',
3809    formContainerCssClass: 'bbmform',
3810    fieldBlockCssClass: 'field',
3811    otherContainerCssClass: 'other-info',
3812    otherFieldSize: 'medium',
3813    fileButtonCssClass: 'file-button',
3814    fileNameCssClass: 'file-name',
3815    fileButtonText: 'Browse',
3816    disclaimerCssClass: 'disclaimer',
3817    consentCheckboxNameSuffix: '_consent',
3818    multipleCheckboxNameSuffix: '_checkbox',
3819    multipleRadioButtonNameSuffix: '_radiobutton',
3820    imageLabelCssClass: 'image-label',
3821    messageContainerCssClass: 'message-container',
3822    loadingImagePath: '//staticfiles.bobitstudios.com/content/loading.gif',
3823    detailedCheckboxCssClass: 'detail-checkbox',
3824    detailedDescriptionCssClass: 'detail-text',
3825    detailedImageCssClass: 'detail-image',
3826    detailedLabelCssClass: 'detail-label',
3827
3828    paymentCartTotalCssClass: 'total',
3829    paymentCartMobileTotalCssClass: 'cart-num',
3830    paymentCartMobileTotalText: 'Added',
3831    paymentCartTotalPriceCssClass: 'total-pricing',
3832    paymentCartHeader: 'Cart Summary',
3833    paymentCheckoutBtnText: 'CHECK OUT',
3834    paymentCheckoutBtnDisableText: 'SELECT ONE',
3835    secureCheckoutUrl: 'https://bobitpayments.com',
3836    //secureCheckoutUrl: "http://localhost:64791"
3837  };
3838
3839  BBMForms.defaultOptions = {
3840    formId: 0,
3841    userIdCookieExpireDays: 60,
3842    hostName: '',
3843    returnUrl: '',
3844    formUrl: '',
3845    userIpAddress: '',
3846    origin: window.location.href, //Default to current url
3847    title: '', //Default to current page title when bbmform gets initialized
3848    callbackGetPassThrough: null,
3849    callbackPostPassThrough: null,
3850    callbackFormLoading: null,
3851    callbackFormLoaded: null,
3852    callbackNextStepLoaded: null,
3853    callbackPrevStepLoaded: null,
3854    callbackBypassStepLoaded: null,
3855    callbackPostError: null,
3856    scrollToTopUponCompletion: true,
3857    enableAutoProgress: false, //this will always be overrided by what the form returns
3858    buttonColor: '3c549d',
3859    $nextBtn: null,
3860    enableConfirmExit: false,
3861    formMetadata: {},
3862    userMetadata: {},
3863    enableMetadataCapture: true, //Enable/disable metadata capture from url by setting false
3864    metadataParamList: [], //pull specific parameters from the url that may not be included in the metadata: default is empty
3865    metadataPrefixList: ['utm_'], //default pulls all parameters that contain "utm_"
3866    loadingTime: 3000,
3867    costLabel: '$',
3868    getFullLeadRecordsBackOnSuccess: false,
3869    allowPrefill: false,
3870  };
3871
3872  BBMForms.conditionalDisplayChangeEvents = [];
3873
3874  BBMForms.run = function () {
3875    var scriptJQuery = document.createElement('script');
3876    scriptJQuery.type = 'text/javascript';
3877    scriptJQuery.src =
3878      '//ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js';
3879    scriptJQuery.onload = function () {
3880      window.bbmFormsJQ = jQuery.noConflict(true);
3881      if (typeof window.onLoadBBMForms !== 'undefined') {
3882        if (bbmFormsJQ.isArray(window.onLoadBBMForms)) {
3883          var onLoadFunc;
3884          onArrayPush(window.onLoadBBMForms, function () {
3885            while (
3886              (onLoadFunc = window.onLoadBBMForms.pop()) !== null &&
3887              typeof onLoadFunc === 'function'
3888            ) {
3889              onLoadFunc();
3890            }
3891          });
3892          while (
3893            (onLoadFunc = window.onLoadBBMForms.pop()) !== null &&
3894            typeof onLoadFunc === 'function'
3895          ) {
3896            onLoadFunc();
3897          }
3898        } else if (typeof window.onLoadBBMForms === 'function') {
3899          window.onLoadBBMForms();
3900        }
3901      }
3902    };
3903    document.head.appendChild(scriptJQuery);
3904  };
3905
3906  BBMForms.getQueryStringValue = function (name) {
3907    var results = getUrlParameter(name);
3908    if (results.length > 0) return results[0];
3909
3910    return '';
3911  };
3912
3913  window.BBMForms = BBMForms;
3914
3915  BBMForms.run();
3916})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.