PageSourceSearch

https://donate.esnorcal.org/_includes/app.lib.js?v=2026-09-22T11:37:44

js esnorcal.org collected 2026-09-29 04:56:47 UTC 664,921 bytes, 17,041 lines download raw bytes

1//IMPORTANT
2//Read Before Editing
3//*******************
4/*
5This file powers core UI functionality that is shared for all web applications.
6No Framework dependencies (e.g. JQuery) should be referenced in this file to ensure the application works without third-party UI dependencies.
7This file is ALWAYS included and contains APP-SPECIFIC reusable functions only.
8
9NOTE: the difference between this and [app name].lib.js is that this is a library contains functionality that is shared for all web apps
10NOTE: the difference between this and app.ui.js is that this is a library file for reusable functions
11
12*/
13//*******************
14
15//framwork UI helpers
16//NOTICE: NO JQUERY or other third-party dependencies
17
18'use strict';
19
20/*App Core Enums*/
21
22const WALLET_TYPES = {
23    NONE: 0,
24    GOOGLE_PAY: 1,
25    APPLE_PAY: 2
26}
27
28const THIRD_PARTY_PAYMENT_PLATFORMS = {
29    None: 0,
30    PayPal: 1,
31    GooglePay: 2,
32    ApplePay: 3,
33    DAFPay: 4
34};
35
36const FORM_VERSIONS = {
37    LEGACY: 1,
38    CURRENT: 2
39};
40
41const FORM_RENDER_VIEWS = {
42    Unknown: 0,
43    LandingPage: 1,
44    ConfirmationPage: 2,
45    ErrorPage: 3,
46    SuccessPage: 4
47};
48
49const FORM_TYPES = {
50    None: 0,
51    Donation: 1,
52	Event_Registration: 2,
53	Volunteer: 3,
54	Enrollment: 4,
55	Contact: 5,
56	Job_Application: 6,
57	Other: 7,
58	Auction: 8,
59	Grassroots_Event_Application: 9,
60	Polls_Surveys: 570,
61	Advocacy: 575,
62	Acquisition: 578,
63	Sustainer_Activation: 603,
64	Membership_Activation: 615,
65	Pledge: 617,
66	Case_Intake: 630,
67	Membership: 665,
68    Volunteer_Time_Entry: 719,
69    Solicitor_Notes: 729
70};
71
72const FORM_SECTION_TYPES = {
73    Custom: 0,
74    Contact_Info: 1,
75	Donation: 2,
76	Event_Application: 7,
77	Event_Ticket: 6,
78	Payment: 4,
79	Payment_Info: 11,
80	Security: 5,
81	Tribute: 3,
82    Volunteer_Time: 12,
83    Free_Gift: 13
84};
85
86const FORM_SYSTEM_FIELD_RENDER_TYPES = {
87    Undefined: 0,
88    Hidden: 5,
89    DropDown: 2,
90    RadioButton: 3,
91    RadioButtonImages: 4,
92    TextBox: 6,
93    CheckBox: 1
94};
95
96const IN_KIND_ASSET_CLASSES = {
97    NONE: 0,
98    PROPERTY: 1,
99    STOCKS: 2,
100    AUTOMOBILES: 3,
101    OTHER: 4,
102    FOOD: 5
103};
104
105const TRIBUTE_TYPES = {
106    HONOR: 1,
107    MEMORY: 2,
108    BEHALF: 3
109};
110
111const OpportunityStates = {
112    Unknown: 0,
113    InProgress: 1,
114    Successful: 2,
115    Failed: 3
116};
117
118/*App Core Utility Functions*/
119
120function getPageHostName() {
121
122    // CORS iFrame use case
123    if (window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0) {
124        try {
125            return new URL(window.location.ancestorOrigins[0]).hostname;
126        }
127        catch (err) {}
128    }
129
130    // same-origin case 
131    try {
132        if (window.parent && window.parent.location && window.parent.location.hostname)
133            return window.parent.location.hostname;
134    }
135    catch (err) {}
136
137    return '';
138}
139
140function initializePublicAppFormExpirationDateTextBoxes() {
141
142    const allFields = document.querySelectorAll('.ce-publicAppFormContainers .ce-formFieldSets .ce-formFieldRows input[type="text"].ce-creditCardExpirationDateTextBoxes');
143
144    for (let x = 0;x < allFields.length;x++) {
145
146        const domElement = allFields[x];
147
148        if (isBlank(domElement.tryGetAttributeValue('placeholder')))
149            domElement.setAttribute('placeholder', 'MM/YY');
150
151        let currentDataValidationAttrVal = domElement.tryGetAttributeValue('data-validation');
152
153        if (currentDataValidationAttrVal == null)
154            currentDataValidationAttrVal = '';
155
156        if (isBlank(currentDataValidationAttrVal))
157            currentDataValidationAttrVal = 'required,creditcardexpirationdate';
158        else if (!currentDataValidationAttrVal.containsValue('creditcardexpirationdate')) {
159            creditcardexpirationdate = creditcardexpirationdate.delimitedListAdd('required');
160            creditcardexpirationdate = creditcardexpirationdate.delimitedListAdd('creditcardexpirationdate');
161        }
162
163        domElement.setAttribute('data-validation', currentDataValidationAttrVal);
164
165        domElement.addEventListener('keyup', function (e) {handleMMYYCreditCardExpirationInputChange(domElement, e);});
166
167    }
168
169}
170
171function toggleFieldLabelVisiblityBasedOnValue(fld) {
172
173    if (fld != null) {
174
175        //console.log('toggle label visiblity for field: ' + fld.id);
176
177        const fldLbl = getLabelForField(fld);
178        if (fldLbl != null) {
179
180            if (fldLbl.hasClassName('ce-alwaysVisibleLabels'))
181                fldLbl.unGhost();
182            else if (isBlank(fld.getValue()))
183                fldLbl.ghost();
184            else
185                fldLbl.unGhost();
186
187        }
188
189    }
190
191}
192
193function initializePublicAppFormDynamicLabel(domElement, fldLbl) {
194
195    if (domElement != null && fldLbl != null && !fldLbl.hasClassName('ce-formFieldNonDynamicLabels')) {
196
197        const fsElem = domElement.firstParentTagByType('fieldset');
198        if (fsElem != null)
199            fsElem.addClassName('ce-hasDynamicLabels');
200
201        domElement.parentElement.addClassName('ce-dynamicLabelContainers');        
202
203        domElement.addValueChangeListener(function (elem) {toggleFieldLabelVisiblityBasedOnValue(elem);}); 
204
205        toggleFieldLabelVisiblityBasedOnValue(domElement);
206
207        //set placeholder from label if not set
208        const lblTxt = getFieldLabelText(fldLbl);
209
210        if (!isBlank(lblTxt) && isBlank(domElement.tryGetAttributeValue('placeholder')))
211            domElement.setAttribute('placeholder', 'Enter ' + lblTxt);
212
213    }
214
215}
216
217function initializePublicAppFormDynamicLabels() {
218
219    const allFields = document.querySelectorAll('.ce-publicAppFormContainers:not(ce-noDynamicLabels) .ce-formFieldSets:not
219(ce-noDynamicLabels) .ce-formFieldRows:not(ce-noDynamicLabels) input[type="text"],.ce-publicAppFormContainers:not(ce-noDynamicLabels) .ce-formFieldSets:not(ce-noDynamicLabels) .ce-formFieldRows:not(ce-noDynamicLabels) input[type="password"],.ce-publicAppFormContainers:not(ce-noDynamicLabels) .ce-formFieldSets:not(ce-noDynamicLabels) .ce-formFieldRows:not(ce-noDynamicLabels) select');
220
221    for (let x = 0;x < allFields.length;x++) {
222
223        const domElement = allFields[x];
224
225        const fldLbl = getLabelForField(domElement);
226
227        initializePublicAppFormDynamicLabel(domElement, fldLbl);
228
229    }
230
231}
232
233function toggleDependentButtonBasedOnFieldValues(depBtnElem) {
234
235    if (depBtnElem != null) {
236
237        const csvFieldDeps = depBtnElem.tryGetCsvAttribute('data-dependentFormFieldDomIdsCsv');
238        if (csvFieldDeps.length > 0)
239            toggleButtonVisiblityBasedOnDependentFieldValues(document.getElementById(csvFieldDeps[0]));
240
241    }
242
243}
244
245function toggleButtonVisiblityBasedOnDependentFieldValues(depFldElem) {
246
247    if (depFldElem != null) {
248
249        const fldButtonDomIdsCsv = depFldElem.tryGetCsvAttribute('data-dependentButtonDomIdsCsv');
250
251        //console.log('toggle dep button for field: ' + depFldElem.id + '(' + fldButtonDomIdsCsv.length + ')');
252
253        for (let x = 0;x < fldButtonDomIdsCsv.length;x++) {
254
255            const dependentBtnElem = document.getElementById(fldButtonDomIdsCsv[x]);
256
257            //console.log('get button: ' + fldButtonDomIdsCsv[x] + '(' + dependentBtnElem + ')');
258
259            if (dependentBtnElem != null) {
260
261                let areAnyFieldsUnpopulated = false;
262
263                const btnDepFldIdsCsv = dependentBtnElem.tryGetCsvAttribute('data-dependentFormFieldDomIdsCsv');
264
265                for (let y = 0;y < btnDepFldIdsCsv.length;y++) {
266
267                    const inputFldElem = document.getElementById(btnDepFldIdsCsv[y]);
268
269                    if (inputFldElem != null && isBlank(inputFldElem.getValue())) {
270                        areAnyFieldsUnpopulated = true;
271                        break;
272                    }
273
274                }
275
276                if (areAnyFieldsUnpopulated)
277                    dependentBtnElem.vanish();
278                else
279                    dependentBtnElem.show();
280
281            }
282
283        }
284
285    }
286
287}
288
289function initializePublicAppFormFieldDependentButtons() {
290
291    //wire up depdendent buttons
292    const dependentButtons = document.querySelectorAll('button[data-dependentFormFieldDomIdsCsv],input[data-dependentFormFieldDomIdsCsv]');
293
294    //console.log('dependentButtons.length: ' + dependentButtons.length);
295
296    for (let x = 0;x < dependentButtons.length;x++) {
297
298        const btnDomId = dependentButtons[x].id;
299
300        //console.log('initialize dependent button: ' + btnDomId);
301
302        if (!isBlank(btnDomId)) {
303
304            const depFldIds = dependentButtons[x].tryGetCsvAttribute('data-dependentFormFieldDomIdsCsv');
305
306            let lastDependentFieldElem = null;
307
308            for (let y = 0;y < depFldIds.length;y++) {
309
310                //console.log('initialize dependent field id: ' + depFldIds[y]);
311
312                const lastDependentFieldElem = document.getElementById(depFldIds[y]);
313                if (lastDependentFieldElem != null) {
314
315                    let curCsv = lastDependentFieldElem.tryGetAttributeValue('data-dependentButtonDomIdsCsv');
316
317                    if (curCsv == null)
318                        curCsv = '';
319
320                    curCsv = curCsv.listAdd(btnDomId);
321
322                    lastDependentFieldElem.setAttribute('data-dependentButtonDomIdsCsv', curCsv);
323
324                    lastDependentFieldElem.addEventListener('input', function () {toggleButtonVisiblityBasedOnDependentFieldValues(this);});
325
326                }
327
328                if (lastDependentFieldElem != null)
329                    toggleButtonVisiblityBasedOnDependentFieldValues(lastDependentFieldElem);
330
331            }
332
333        }
334        else
335            console.log('unable to initialize dependent button (dom id required)');
336
337    }
338
339}
340
341function initializePublicAppFormValidatedFields() {
342
343    const allFields = document.querySelectorAll('.ce-publicAppFormContainers .ce-formFieldSets .ce-formFieldRows input[type="text"],.ce-publicAppFormContainers .ce-formFieldSets .ce-formFieldRows input[type="checkbox"],.ce-publicAppFormContainers .ce-formFieldSets .ce-formFieldRows input[type="password"],.ce-publicAppFormContainers .ce-formFieldSets .ce-formFieldRows textarea,.ce-publicAppFormContainers .ce-formFieldSets .ce-formFieldRows select'); //,span.checkbox-set,div.searchableDropDownListWithListBuilderContainers
344
345    for (let x = 0;x < allFields.length;x++) {
346
347        const domElement = allFields[x];
348
349        if (!isBlank(domElement.getAttribute('data-validation'))) {
350
351            const formFieldType = domElement.getInputFieldType();
352            if (formFieldType != FormInputTypes.Unknown) {
353
354                //revalidate if there's a change
355                switch (formFieldType) {
356                    case FormInputTypes.TextBox:
357                    case FormInputTypes.Password:
358                    case FormInputTypes.TextArea:
359                        domElement.addEventListener('keyup', function () {this.validate();});
360                        break;
361                    case FormInputTypes.CheckBox:
362                        domElement.addEventListener('click', function () {this.validate();});
363                        break;
364                    case FormInputTypes.DropDownList:
365                        domElement.addEventListener('change', function () {this.validate();});
366                        break;
367                }
368
369            }
370
371        }
372
373    }
374
375}
376
377function getFormItemQuantityInputValue(itemQuantityInputElem) {
378
379    let retVal = 0;
380
381    if (itemQuantityInputElem != null) {
382
383        if (itemQuantityInputElem.tagName.toLowerCase() == 'input' && itemQuantityInputElem.getAttribute('type') == 'checkbox') {
384
385            if (itemQuantityInputElem.checked)
386                retVal = 1;
387
388        }
389        else if (!isBlank(itemQuantityInputElem.getValue()))
390            retVal = itemQuantityInputElem.getValue().toString().toIntZeroIfInvalid();
391
392    }
393
394    return retVal;
395
396}
397
398function getFormTicketQuantityInputValue(ticketQuantityInputElem) {
399
400    return getFormItemQuantityInputValue(ticketQuantityInputElem);
401
402}
403
404function getFormTicketQuantityInputMaxValue(ticketQuantityInputElem) {
405
406    let retVal = 0;
407
408    if (ticketQuantityInputElem != null) {
409
410        let maxValRaw = '';
411
412        if (ticketQuantityInputElem.tagName.toLowerCase() == 'input' && ticketQuantityInputElem.getAttribute('type') == 'checkbox')
413            maxValRaw = ticketQuantityInputElem.parentNode.tryGetAttributeValue('data-maxvalue');
414        else
415            maxValRaw = ticketQuantityInputElem.tryGetAttributeValue('data-maxvalue');
416
417        if (!isBlank(maxValRaw))
418            retVal = maxValRaw.toString().toIntZeroIfInvalid();
419
420    }
421
422    return retVal;
423
424}
425
426function getFormTicketQuantityInputTicketId(ticketQuantityInputElem) {
427
428    let retVal = 0;
429
430    if (ticketQuantityInputElem != null) {
431
432        let maxValRaw = '';
433
434        if (ticketQuantityInputElem.tagName.toLowerCase() == 'input' && ticketQuantityInputElem.getAttribute('type') == 'checkbox')
435            maxValRaw = ticketQuantityInputElem.parentNode.tryGetAttributeValue('data-ticket-id');
436        else
437            maxValRaw = ticketQuantityInputElem.tryGetAttributeValue('data-ticket-id');
438
439        if (!isBlank(maxValRaw))
440            retVal = maxValRaw.toString().toIntZeroIfInvalid();
441
442    }
443
444    return retVal;
445
446}
447
448function roundEndUserSuggestedDonationAmount(amt) {
449
450    let retVal = 0.00;
451
452    if (!isBlank(amt) && amt.toString().toDecimalZeroIfInvalid() > 0) {
453
454        let decVal = amt.toString().toDecimalZeroIfInvalid();
455
456        retVal = (Math.round(decVal / 5) * 5);
457
458        ////hard rule - don't ask for more than $500 on a donation form
459        //if (decVal >= 1000)
460        //    retVal = Math.round(decVal / 100) * 100; //retVal = 900;
461        //else if (decVal > 100) 
462        //    retVal = Math.round(decVal / 50) * 50;
463        //else if (decVal > 10) 
464        //    retVal = Math.ceil(decVal / 10) * 10; //deliberately roundind up
465        //else
466        //    retVal = Math.ceil(decVal / 5) * 5; //deliberately roundind up
467
468    }
469
470    if (retVal < 5)
471        retVal = 5;
472
473    return retVal;
474
475}
476
477function getEndUserSuggestedDonationIncrementFromAmount(amt) {
478
479    let retVal = 0.00;
480
481    if (!isBlank(amt) && amt.toString().toDecimalZeroIfInvalid() > 0) {
482
483        let decVal = amt.toString().toDecimalZeroIfInvalid();
484
485        //hard rule - don't ask for more than $500 on a donation form
486        if (decVal > 500)
487            retVal = 100;
488        else if (decVal > 100)
489            retVal = 50;
490        else if (decVal > 10)
491            retVal = 10;
492        else
493            retVal = 5;
494
495    }
496
497    return retVal;
498
499}
500
501function getEndUserSuggestionAskAmountsFromMinimum(amt, numberOfOptions, seedMultiplier) {
502
503    let retVal = [];
504
505    if (numberOfOptions <= 0)
506        numberOfOptions = 5;
507
508    let tmpAmt = amt;
509
510    if (seedMultiplier > 0 && !isBlank(amt) && amt.toString().toDecimalZeroIfInvalid() > 0)
511        tmpAmt = (tmpAmt * seedMultiplier);
512
513    let decVal = roundEndUserSuggestedDonationAmount(tmpAmt);
514
515    if (decVal > 0) {
516
517        let incVal = getEndUserSuggestedDonationIncrementFromAmount(decVal);
518
519        retVal.push(decVal);
520
521        if (numberOfOptions > 1)
522            for (let x = 0;x < (numberOfOptions - 1);x++) {
523
524                decVal = decVal + incVal;
525
526                retVal.push(decVal);
527
528            }
529
530    }
531
532    return retVal;
533
534}
535
536function getEndUserSuggestionAskAmountsFromMaximum(amt, numberOfOptions, seedMultiplier) {
537
538    let retVal = [];
539
540    if (numberOfOptions <= 0)
541        numberOfOptions = 5;
542
543    let tmpAmt = amt;
544
545    if (seedMultiplier > 0 && !isBlank(amt) && amt.toString().toDecimalZeroIfInvalid() > 0)
546        tmpAmt = (tmpAmt * seedMultiplier);
547
548    let decVal = roundEndUserSuggestedDonationAmount(tmpAmt);
549
550    if (decVal > 0) {
551
552        let incVal = getEndUserSuggestedDonationIncrementFromAmount(decVal); // * numberOfOptions
553
554        if ((decVal - (incVal * numberOfOptions)) <= 0)
555            decVal = ((incVal * numberOfOptions)); // + 5);
556
557        retVal.push(decVal);
558
559        if (numberOfOptions > 1)
560            for (let x = 0;x < (numberOfOptions - 1);x++) {
561
562                decVal = decVal - incVal;
563
564                retVal.push(decVal);
565
566            }
567
568        retVal = retVal.reverse();
569
570    }
571
572    return retVal;
573
574}
575
576function ValidatePromoCode(promoCode, paymentAmount, selectedTickets, form_id, successCallBack, errorCallBack) {
577
578    const params = {
579        "ajaxParams": {
580            "PromoCode": promoCode,
581            "PaymentAmount": paymentAmount,
582            "Form_Id": form_id,
583            "SelectedTickets": selectedTickets
584        }
585    };
586
587    App.PostJsonAsync('/api/PublicWebAppUIAjaxEndpoint/ValidatePromoCode',
588        JSON.stringify(params),
589        function (result) {
590
591            //console.log('validate promo code: ' + result);
592
593            if (typeof successCallBack == 'function')
594                successCallBack(result);
595
596        },
597        function (error) {
598
599            //console.log('error: ' + error);
600
601            if (typeof errorCallBack == 'function')
602                errorCallBack(error);
603
604        });
605
606}
607
608function LookupPostalCodeLocationInfo(postalCode, successCallBack, errorCallBack) {
609
610    const params = {
611        "postalCode": postalCode
612    };
613
614    App.PostJsonAsync('/api/PublicWebAppUIAjaxEndpoint/GetPostalCodeLocationInfo',
615        JSON.stringify(params),
616        function (result) {
617
618            //console.log('postal code: ' + result);
619
620            if (typeof successCallBack == 'function')
621                successCallBack(result);
622
623        },
624        function (error) {
625
626            //console.log('error: ' + error);
627
628            if (typeof errorCallBack == 'function')
629                errorCallBack(error);
630
631        });
632
633}
634
635
636/*App Core Helper Apis*/
637 
638function getVerticalScollingPanelApi(scrollElemId, scrollEntryCssSelector, minimumNumberOfEntries, scrollStartDelayMilliseconds) {
639
640    const thisApi = {};
641
642    thisApi.ScrollElemId = scrollElemId;
643
644    thisApi.ScrollEntryCssSelector = scrollEntryCssSelector;    
645
646    thisApi.ScrollStartDelayMilliseconds = scrollStartDelayMilliseconds;    
647    thisApi.MinimumNumberOfEntries = minimumNumberOfEntries;    
648
649    const _logger = getConsoleLogApi({ "LogSourceName": 'CE Vertical Scrolling Panel Api' });
650
651    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
652
653        CallBackManager.add('verticalScollingPanelApiInitializedCallBack', callBackFunction, null);
654
655    };
656
657    thisApi.FireInitializedEvent = function () {
658
659        CallBackManager.fire('verticalScollingPanelApiInitializedCallBack', {}, null);
660
661    };
662
663    //Initialize
664    thisApi.Initialize = function () {
665
666        _logger.Log('initialize started');
667
668        if (!isBlank(thisApi.ScrollElemId) && document.getElementById(thisApi.ScrollElemId) != null) {
669
670            //add auto-scroll class to parent
671            document.getElementById(thisApi.ScrollElemId).parentElement.addClassName('autoScrollingWidgetBoxes');
672
673            //try to set to first child if possible
674            if (isBlank(thisApi.ScrollEntryCssSelector)) {
675
676                //_logger.Log('verticalScollingPanelApi ScrollElemId id ' + document.getElementById(thisApi.ScrollElemId).id);
677                //_logger.Log('verticalScollingPanelApi first child tag ' + document.getElementById(thisApi.ScrollElemId).firstElementChild .TagName);
678
679                if (document.getElementById(thisApi.ScrollElemId).firstElementChild != null && !isBlank(document.getElementById(thisApi.ScrollElemId).firstElementChild.tagName)) {
680
681                    let childTgName = document.getElementById(thisApi.ScrollElemId).firstElementChild.tagName;
682
683                    _logger.Log('verticalScollingPanelApi auto-configured scroll entry selector to first child tag ' + childTgName + ' (user parameter to specify entry tag selector)');
684
685                    thisApi.ScrollEntryCssSelector = childTgName;
686
687                    //special handling case for tables, where there may be a tbody tag
688                    if (childTgName.toLowerCase() == 'tbody' && document.getElementById(thisApi.ScrollElemId).firstElementChild.firstElementChild != null && !isBlank(document.getElementById(thisApi.ScrollElemId).firstElementChild.firstElementChild.tagName)) {
689
690                        _logger.Log('verticalScollingPanelApi detected  tbody child and found better aut-configure scroll entry selector ' + document.getElementById(thisApi.ScrollElemId).firstElementChild.firstElementChild.tagName + ' (user parameter to specify entry tag selector)');
691
692                        thisApi.ScrollEntryCssSelector = document.getElementById(thisApi.ScrollElemId).firstElementChild.firstElementChild.tagName;
693
694                    }
695                    
696                }
697
698            }
699
700            if (!isBlank(thisApi.ScrollEntryCssSelector)) { 
701
702                initializeVerticalScrollingElement(document.getElementById(thisApi.ScrollElemId), thisApi.ScrollEntryCssSelector, thisApi.MinimumNumberOfEntries, thisApi.ScrollStartDelayMilliseconds);
703
704                thisApi.FireInitializedEvent();
705
706            }
707            else
708                _logger.Log('unable to initialize verticalScollingPanelApi becuase scroll entry CSS selector is missing');
709
710        }
711        else
712            _logger.Log('unable to initialize verticalScollingPanelApi becuase scroll element id ' + thisApi.ScrollElemId + ' is invalid');
713
714    };
715
716    return thisApi;
717
718}
719 
720function getListPopBoxPanelApi(containerElemId, listEntriesArray, listDataAjaxUrl, popTimerSeconds, addClassNames) {
721
722    const thisApi = {};
723
724    thisApi.ContainerElemId = containerElemId;
725
726    thisApi.ListEntriesArray = listEntriesArray;    
727
728    thisApi.ListDataAjaxUrl = listDataAjaxUrl;  //future
729
730    thisApi.PopTimerMilliSeconds = 10000; //default to 10 seconds
731
732    if (popTimerSeconds > 0)
733        thisApi.PopTimerMilliSeconds = (popTimerSeconds * 1000);
734
735    thisApi.AddClassNames = addClassNames;
736
737    const _logger = getConsoleLogApi({ "LogSourceName": 'CE List Pop Box Panel Api' });
738
739    let _currentEntryIndex = -1;
740
741    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
742
743        CallBackManager.add('listPopBoxPanelApiInitializedCallBack', callBackFunction, null);
744
745    };
746
747    thisApi.FireInitializedEvent = function () {
748
749        CallBackManager.fire('listPopBoxPanelApiInitializedCallBack', {}, null);
750
751    };
752
753    thisApi.GetContainerElement = function () { return document.getElementById(thisApi.ContainerElemId); };
754
755    thisApi.GetEntriesContainerElement = function () {
756
757        let retVal = null;
758
759        if (thisApi.GetContainerElement() != null)
760            retVal = thisApi.GetContainerElement().querySelector('div.onlineListWidgetPopBoxes');
761
762        return retVal;
763
764    };
765
766    thisApi.Show = function () {
767
768        if (thisApi.GetContainerElement() != null)
769            thisApi.GetContainerElement().show();
770
771    };
772
773    thisApi.Hide = function () {
774
775        if (thisApi.GetContainerElement() != null)
776            thisApi.GetContainerElement().vanish();
777
778    };
779
780    thisApi.Pause = function () {
781         
782        if (thisApi.GetContainerElement() != null)
783            thisApi.GetContainerElement().setAttribute('data-updates-paused', '1');
784
785    };
786
787    thisApi.UnPause = function () {
788         
789        if (thisApi.GetContainerElement() != null)
790            thisApi.GetContainerElement().setAttribute('data-updates-paused', '0');
791
792    };
793
794    thisApi.Stop = function () {
795         
796        if (thisApi.GetContainerElement() != null)
797            thisApi.GetContainerElement().setAttribute('data-updates-stopped', '1');
798
799        thisApi.Hide();
800
801    };
802
803    const _processUpdates = function () {
804
805        //this kills all updates becuase call back never fires resart
806        let isStopped = thisApi.GetContainerElement().tryGetTrueFalseAttribute('data-updates-stopped');
807        if (!isStopped) {
808             
809            let isPaused = thisApi.GetContainerElement().tryGetTrueFalseAttribute('data-updates-paused');
810            if (!isPaused) {
811
812                //always clear first to allow for visual delay
813                thisApi.GetEntriesContainerElement().innerHTML = '';
814                thisApi.Hide();
815
816                _logger.Log('process pop box entry finished index ' + _currentEntryIndex + ' clear entry and wait before showing new entry');
817
818                setTimeout(
819                    function () {
820
821                        _currentEntryIndex++;
822
823                        if (_currentEntryIndex >= thisApi.ListEntriesArray.length)
824                            _currentEntryIndex = 0; // start over
825
826                        let newEntryHtml = thisApi.ListEntriesArray[_currentEntryIndex];
827
828                        _logger.Log('process pop box entry ' + _currentEntryIndex + ' of ' + thisApi.ListEntriesArray.length + ' after ' + thisApi.PopTimerMilliSeconds + ' millisecond delay');
829                 
830                        thisApi.GetEntriesContainerElement().innerHTML = newEntryHtml;
831                        thisApi.Show();
832
833                        setTimeout(function () { _processUpdates() }, thisApi.PopTimerMilliSeconds);
834
835                    },
836                    thisApi.PopTimerMilliSeconds); //reuse timer for both waits for now
837                
838            }
839            else
840                setTimeout(function () { _processUpdates() }, thisApi.PopTimerMilliSeconds);
841
842        }
843        else
844            thisApi.Hide(); //belt & suspenders
845
846    };
847
848    //Initialize
849    thisApi.Initialize = function () {
850
851        _logger.Log('initialize started');
852
853        if (thisApi.GetContainerElement() != null) {
854
855            thisApi.Hide(); //belt & suspenders
856
857            //try to set to first child if possible
858            if ((thisApi.ListEntriesArray != null && thisApi.ListEntriesArray.length > 0) || !isBlank(thisApi.ListDataAjaxUrl)) {
859
860                thisApi.GetContainerElement().addEventListener('mouseover', function () { thisApi.Pause(); });
861                thisApi.GetContainerElement().addEventListener('mouseout', function () { thisApi.UnPause(); });
862
863                //add entries element if not popualted
864                let entriesElem = thisApi.GetEntriesContainerElement();
865                if (entriesElem == null) {
866
867                    _logger.Log('initialize getListPopBoxPanelApi adding required DOM elements');
868
869                    //add entries
870                    let entriesElem = document.createElement('div');
871                    entriesElem.addClassName('onlineListWidgetPopBoxes');
872
873                    if (!isBlank(thisApi.AddClassNames))
874                        entriesElem.addClassNames(thisApi.AddClassNames);
875
876                    thisApi.GetContainerElement().appendChild(entriesElem);
877                     
878                    //add close icon
879                    let closeIconCnt = document.createElement('div');
880
881                    closeIconCnt.addClassNames('onlineListWidgetBoxOffsetCloseIconContainers boxPositionBottomLeft');
882
883                    if (!isBlank(thisApi.AddClassNames))
884                        closeIconCnt.addClassNames(thisApi.AddClassNames);
885
886                    //close circle
887                    let closeIconCrcl = document.createElement('div');
888
889                    closeIconCrcl.addEventListener('click', function () { thisApi.Hide(); });
890
891                    closeIconCrcl.addClassNames('onlineListWidgetBoxOffsetCloseIconCircles');
892
893                    if (!isBlank(thisApi.AddClassNames))
894                        closeIconCrcl.addClassNames(thisApi.AddClassNames);
895
896                    //close text
897                    let closeIconTxt = document.createElement('div');
898
899                    closeIconTxt.addClassNames('onlineListWidgetBoxOffsetCloseIconText');
900
901                    if (!isBlank(thisApi.AddClassNames))
902                        closeIconTxt.addClassNames(thisApi.AddClassNames);
903
904                    closeIconTxt.innerText = 'X';
905
906                    closeIconCrcl.appendChild(closeIconTxt);
907
908                    closeIconCnt.appendChild(closeIconCrcl);
909
910                    thisApi.GetContainerElement().appendChild(closeIconCnt);
911
912                }
913
914                _processUpdates();                
915
916                thisApi.FireInitializedEvent();
917            
918            }
919            else
920                _logger.Log('unable to initialize getListPopBoxPanelApi becuase data array is missing or emptry and no real-time ajax URL is available');
921
922        }
923        else
924            _logger.Log('unable to initialize getListPopBoxPanelApi becuase container element id ' + thisApi.ContainerElemId + ' is invalid');
925
926    };
927
928    return thisApi;
929
930}
931 
932function getEndUserSuggestedDonationAskAmountsApi(seedAmount, numberOfOptions, seedMultiplier) {
933
934    const thisApi = {};
935
936    thisApi.SeedAmount = seedAmount;
937    thisApi.NumberOfOptions = numberOfOptions;
938
939    thisApi.SeedMultiplier = 1;
940
941    if (seedMultiplier > 0)
942        thisApi.SeedMultiplier = seedMultiplier;
943
944    thisApi.GenerateAmountsFromSeed = function () { return getEndUserSuggestionAskAmountsFromMinimum(thisApi.SeedAmount, thisApi.NumberOfOptions, thisApi.SeedMultiplier); };
945    thisApi.GenerateAmountsToSeed = function () { return getEndUserSuggestionAskAmountsFromMaximum(thisApi.SeedAmount, thisApi.NumberOfOptions, thisApi.SeedMultiplier); };
946
947    return thisApi;
948
949}
950
951function getAddAdditionalContactsApi(config) {
952
953    const thisApi = {};
954
955    //thisApi.Type = config.Type; //if needed (i.e., registraiton verus membership)
956
957    thisApi.AutoInitialize = (config.AutoInitialize != null) ? config.AutoInitialize : true;
958
959    thisApi.ItemQuantityFieldDomId = config.ItemQuantityFieldDomId;
960
961    thisApi.RelatedElementContainerDomId = config.RelatedElementContainerDomId;
962
963    thisApi.WidgetContainerDomId = config.WidgetContainerDomId;
964
965    thisApi.MaximumContactsPerItem = (config.MaximumContactsPerItem != null) ? config.MaximumContactsPerItem : 0;
966    thisApi.MaximumContacts = (config.MaximumContacts != null) ? config.MaximumContacts : 0;
967
968    let _getMaximumContactsFunction = null;
969
970    if (config.GetMaximumContactsFunction != null && typeof config.GetMaximumContactsFunction == 'function')
971        _getMaximumContactsFunction = config.GetMaximumContactsFunction;
972
973    thisApi.EmailAddressRequired = (config.EmailAddressRequired != null) ? config.EmailAddressRequired : false;
974    thisApi.PhoneNumberRequired = (config.PhoneNumberRequired != null) ? config.PhoneNumberRequired : false;
975
976    thisApi.TitleHtml = config.TitleHtml;
977
978    thisApi.AddContactButtonText = (!isBlank(config.AddContactButtonText)) ? config.AddContactButtonText : 'Add Another';
979
980    thisApi.AddFirstContactRow = (config.AddFirstContactRow != null) ? config.AddFirstContactRow : true;
981
982    thisApi.RelatedElementReference_Id = (config.RelatedElementReference_Id != null) ? config.RelatedElementReference_Id : 0;
983
984    thisApi.HiddenFieldDomId = config.HiddenFieldDomId;
985
986    const _logger = getConsoleLogApi({ "LogSourceName": 'CE Add More Contacts Api' });
987
988    thisApi.GetItemQuantityFieldElement = function () { return document.getElementById(thisApi.ItemQuantityFieldDomId); };
989
990    thisApi.GetItemQuantity = function () {
991
992        let retVal = 0;
993
994        const itemQuantElem = thisApi.GetItemQuantityFieldElement();
995
996        if (itemQuantElem != null)
997            retVal = getFormItemQuantityInputValue(itemQuantElem);
998        else
999            retVal = 1; // if quanity selector not defined, hard-code 1 (e.g. membership sign up - always 1 no matter what unlike tickets)
1000
1001        return retVal;
1002
1003    };
1004
1005    thisApi.GetRelatedElementContainerElement = function () { return document.getElementById(thisApi.RelatedElementContainerDomId); };
1006
1007    thisApi.GetWidgetContainerElement = function () {
1008
1009        let contElem = document.getElementById(thisApi.WidgetContainerDomId);
1010
1011        if (contElem == null)
1012            contElem = thisApi.GetAddContactsWidgetElement(); //fall back (legacy forms mostly)
1013
1014        return contElem;
1015
1016    };
1017     
1018    thisApi.Show = function () { if (thisApi.GetWidgetContainerElement() != null) thisApi.GetWidgetContainerElement().show(); };
1019    thisApi.Hide = function () { if (thisApi.GetWidgetContainerElement() != null) thisApi.GetWidgetContainerElement().vanish(); };
1020
1021    const _getUniqueDomIdPrefix = function () { return thisApi.RelatedElementContainerDomId + '_' + thisApi.WidgetContainerDomId; };
1022
1023    thisApi.GetAddContactsWidgetElement = function () { return document.getElementById(_getUniqueDomIdPrefix() + '_additionalContactsContainer'); };
1024
1025    thisApi.GetContactRowsContainerElement = function () { return document.getElementById(_getUniqueDomIdPrefix() + '_additionalContactsContactRowsContainer'); };
1026
1027    thisApi.GetAddContactButtonElement = function () { return document.getElementById(_getUniqueDomIdPrefix() + '_addAn
1027otherButton'); };
1028
1029    thisApi.GetAdditionalContactRows = function () { return thisApi.GetContactRowsContainerElement().querySelectorAll('.additionalContactsContactRows'); }
1030    thisApi.GetAdditionalContactRowCount = function () { return thisApi.GetContactRowsContainerElement().querySelectorAll('.additionalContactsContactRows').length; }
1031
1032    thisApi.HandleItemQuantityChanged = function () {
1033
1034        const contElem = thisApi.GetWidgetContainerElement();        
1035        if (contElem != null) {
1036
1037            contElem.vanish();
1038
1039            const itemQuant = thisApi.GetItemQuantity();            
1040            if (itemQuant > 0) { 
1041                 
1042                const maxContactsAllowed = _getNumberOfAllowedContacts();         
1043         
1044                //make sure we don't have a dynamic evaluation (function)
1045                const maxDynamic = (_getMaximumContactsFunction != null && typeof _getMaximumContactsFunction == 'function');
1046          
1047                //only show the container if we can allow any contacts
1048                //NOTE: this might be over complicated, but idea is to possibly allow unlimited via non-dynamic config at some point - can be simplified if its not worth it and we want to force a max per item even if its 1 (5.28.26)
1049                if (maxContactsAllowed > 0 || !maxDynamic) { 
1050
1051                    contElem.show();
1052
1053                    _refreshAdditionalContactRowsBasedOnMax();
1054
1055                }
1056
1057            }
1058             
1059        }
1060
1061        _toggleAddGuestButton();
1062
1063    };
1064
1065    const _refreshAdditionalContactRowsBasedOnMax = function () {
1066
1067        const maxContactsAllowed = _getNumberOfAllowedContacts();
1068
1069        let currentCount = thisApi.GetAdditionalContactRowCount();
1070        while (currentCount > 0 && currentCount > maxContactsAllowed) {
1071
1072            //remove last
1073            let rows = thisApi.GetAdditionalContactRows();
1074            if (rows != null && rows.length > 0)
1075                thisApi.TryRemoveContactRow(getLastArrayEntry(rows), false);
1076
1077            currentCount = thisApi.GetAdditionalContactRowCount();
1078
1079        }
1080
1081    };
1082
1083    thisApi.SetMaximumContactsPerItem = function (newMax) {
1084
1085        thisApi.MaximumContactsPerItem = newMax;
1086
1087        let maxContactsAllowed = _getNumberOfAllowedContacts();
1088
1089        if (maxContactsAllowed > 0) {
1090
1091            _refreshAdditionalContactRowsBasedOnMax();
1092
1093            _toggleAddGuestButton();  
1094
1095        }
1096
1097    };
1098
1099    thisApi.ValidateRows = function () {
1100
1101        let retVal = true;
1102
1103        let contactRows = thisApi.GetAdditionalContactRows();
1104        for (let x = 0; x < contactRows.length; x++) {
1105
1106            let firstNameElem = contactRows[x].querySelector('.additionalContactsContactFirstNames');
1107            firstNameElem.validationSucceeded();
1108            if (isBlank(firstNameElem.getValue())) {
1109                retVal = false;
1110                firstNameElem.validationFailed();
1111            }
1112
1113            let lastNameElem = contactRows[x].querySelector('.additionalContactsContactLastNames');
1114            lastNameElem.validationSucceeded();
1115            if (isBlank(lastNameElem.getValue())) {
1116                retVal = false;
1117                lastNameElem.validationFailed();
1118            }
1119
1120            let emailElem = contactRows[x].querySelector('.additionalContactsContactEmailAddresses');
1121            emailElem.validationSucceeded();
1122            if (thisApi.EmailAddressRequired && isBlank(emailElem.getValue())) {
1123                retVal = false;
1124                emailElem.validationFailed();
1125            }
1126
1127            if (!isBlank(emailElem.getValue()) && !isValidEmailAddress(emailElem.getValue())) {
1128                retVal = false;
1129                emailElem.validationFailed();
1130            }
1131
1132            let phoneElem = contactRows[x].querySelector('.additionalContactsContactPhoneNumbers');
1133            phoneElem.validationSucceeded();
1134            if (thisApi.PhoneNumberRequired && isBlank(phoneElem.getValue())) {
1135                retVal = false;
1136                phoneElem.validationFailed();
1137            }
1138
1139        }
1140
1141        return retVal;
1142
1143    }
1144
1145    thisApi.GetContactDetails = function () {
1146
1147        let retVal = [];
1148
1149        let contactRows = thisApi.GetAdditionalContactRows();
1150        for (let x = 0; x < contactRows.length; x++) {
1151
1152            let cnt = {};
1153
1154            cnt.FirstName = contactRows[x].querySelector('.additionalContactsContactFirstNames').getValue();
1155            cnt.LastName = contactRows[x].querySelector('.additionalContactsContactLastNames').getValue();
1156            cnt.EmailAddress = contactRows[x].querySelector('.additionalContactsContactEmailAddresses').getValue();
1157            cnt.PhoneNumber = contactRows[x].querySelector('.additionalContactsContactPhoneNumbers').getValue();
1158
1159            retVal.push(cnt);
1160
1161        }
1162
1163        return retVal;
1164
1165    };
1166
1167    thisApi.GetSerializedContactDetails = function () {
1168
1169        let cntArr = thisApi.GetContactDetails();
1170
1171        return JSON.stringify(cntArr);
1172
1173    };
1174
1175    thisApi.WriteSerializedContactDetailsToHiddenField = function () {
1176
1177        if (!isBlank(thisApi.HiddenFieldDomId) && document.getElementById(thisApi.HiddenFieldDomId) != null) {
1178
1179            let serCnt = thisApi.GetSerializedContactDetails();
1180
1181            document.getElementById(thisApi.HiddenFieldDomId).setValue(serCnt);
1182
1183        }
1184
1185    };
1186
1187    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
1188
1189        CallBackManager.add(_getUniqueDomIdPrefix() + '_addAdditionalContactsApiInitializedCallBack', callBackFunction, null);
1190
1191    };
1192
1193    const _fireInitializedEvent = function () {
1194
1195        let returnObj = {};
1196        returnObj.RelatedElementContainerDomId = thisApi.RelatedElementContainerDomId; //note can have multiple per page
1197
1198        CallBackManager.fire(_getUniqueDomIdPrefix() + '_addAdditionalContactsApiInitializedCallBack', returnObj, null);
1199
1200    };
1201
1202    const _getNumberOfAllowedContacts = function () {
1203
1204        const curQty = thisApi.GetItemQuantity();
1205         
1206        let retVal = (curQty * thisApi.MaximumContactsPerItem);
1207        
1208        if (thisApi.MaximumContacts > 0 && retVal > thisApi.MaximumContacts)
1209            retVal = thisApi.MaximumContacts;   
1210
1211        //console.log(thisApi.WidgetContainerDomId + ' - _getNumberOfAllowedContacts curQty: ' + curQty);
1212
1213        if (_getMaximumContactsFunction != null && typeof _getMaximumContactsFunction == 'function') 
1214            retVal = _getMaximumContactsFunction(curQty);                       
1215
1216        return retVal;
1217
1218    };
1219
1220    const _toggleAddGuestButton = function () {
1221
1222        //check for add another
1223        const addAnotherBtn = thisApi.GetAddContactButtonElement(); 
1224
1225        const maxContactsAllowed = _getNumberOfAllowedContacts();         
1226         
1227        //make sure we don't have a dynamic evaluation (function)
1228        const maxDynamic = (_getMaximumContactsFunction != null && typeof _getMaximumContactsFunction == 'function');
1229         
1230        const currentUIRowCount = thisApi.GetAdditionalContactRowCount();
1231         
1232        addAnotherBtn.vanish();
1233        if ((maxContactsAllowed <= 0 && !maxDynamic) || currentUIRowCount < maxContactsAllowed) 
1234            addAnotherBtn.show();                       
1235 
1236    };
1237
1238    thisApi.TryRemoveContactRow = function (rowParentElem, callToggleAddAnotherButton) {
1239
1240        const rowEntryRowsPrntElem = thisApi.GetContactRowsContainerElement();
1241        if (rowEntryRowsPrntElem != null && rowParentElem != null) 
1242            rowParentElem.remove();        
1243
1244        if(callToggleAddAnotherButton)
1245            _toggleAddGuestButton();
1246
1247    };
1248
1249    thisApi.AddContactRow = function () {
1250
1251        let rowEntryRowsPrntElem = thisApi.GetContactRowsContainerElement();
1252        if (rowEntryRowsPrntElem != null) {
1253             
1254            let entryRow = document.createElement('div');            
1255            entryRow.addClassName('additionalContactsContactRows');
1256
1257            //add first name
1258            let firstNameElem = document.createElement('input');
1259            firstNameElem.setAttribute('type', 'text');                        
1260            firstNameElem.addClassName('additionalContactsContactFirstNames');
1261            firstNameElem.setAttribute('placeholder', 'First Name');
1262
1263            entryRow.appendChild(firstNameElem);
1264
1265            //add last name
1266            let lastNameElem = document.createElement('input');
1267            lastNameElem.setAttribute('type', 'text');                        
1268            lastNameElem.addClassName('additionalContactsContactLastNames');
1269            lastNameElem.setAttribute('placeholder', 'Last Name');
1270
1271            entryRow.appendChild(lastNameElem);
1272
1273            //add email
1274            let emailElem = document.createElement('input');
1275            emailElem.setAttribute('type', 'text');                        
1276            emailElem.addClassName('additionalContactsContactEmailAddresses');
1277            emailElem.setAttribute('placeholder', 'Email');
1278
1279            entryRow.appendChild(emailElem);
1280
1281            //add phone
1282            let phoneElem = document.createElement('input');
1283            phoneElem.setAttribute('type', 'text');                        
1284            phoneElem.addClassName('additionalContactsContactPhoneNumbers');
1285            phoneElem.setAttribute('placeholder', 'Phone');
1286
1287            entryRow.appendChild(phoneElem);
1288
1289            //if (thisApi.GetAdditionalContactRowCount() > 0) { 
1290
1291                let removeBtn = document.createElement('input');
1292                removeBtn.setAttribute('type', 'button');
1293                removeBtn.setAttribute('value', 'Remove');
1294                removeBtn.addEventListener('click', function () { thisApi.TryRemoveContactRow(this.parentNode, true); });
1295
1296                entryRow.appendChild(removeBtn);
1297
1298            //}
1299
1300            rowEntryRowsPrntElem.appendChild(entryRow);
1301
1302            _toggleAddGuestButton();        
1303
1304        }
1305
1306    };
1307
1308    let _initialized = false;
1309
1310    //Initialize
1311    thisApi.Initialize = function () {
1312
1313        _logger.Log('initialize started (' + _getUniqueDomIdPrefix() + ')');        
1314
1315        if (!_initialized) {
1316
1317            let parentElem = document.getElementById(thisApi.WidgetContainerDomId);
1318
1319            if (parentElem == null)
1320                parentElem = thisApi.GetRelatedElementContainerElement(); //fall back (legacy forms mostly)
1321                 
1322            if (parentElem != null) {
1323
1324                let additionalContactsContainer = document.createElement('div');
1325                additionalContactsContainer.id = _getUniqueDomIdPrefix() + '_additionalContactsContainer';
1326                additionalContactsContainer.addClassName('additionalContactsContainers');
1327                 
1328                if (!isBlank(thisApi.TitleHtml)) {
1329
1330                    let additionalContactsContactsTitleContainer = document.createElement('div');
1331                    additionalContactsContactsTitleContainer.id = _getUniqueDomIdPrefix() + '_additionalContactsContactTitleContainer';
1332                    additionalContactsContactsTitleContainer.addClassName('additionalContactsContactsTitleContainers');
1333
1334                    additionalContactsContactsTitleContainer.innerHTML = thisApi.TitleHtml;
1335
1336                    additionalContactsContainer.appendChild(additionalContactsContactsTitleContainer);
1337
1338                }
1339
1340                let additionalContactsContactRowsContainer = document.createElement('div');
1341                additionalContactsContactRowsContainer.id = _getUniqueDomIdPrefix() + '_additionalContactsContactRowsContainer';
1342                additionalContactsContactRowsContainer.addClassName('additionalContactsContactRowsContainers');
1343                 
1344                additionalContactsContainer.appendChild(additionalContactsContactRowsContainer);
1345
1346                //add button
1347                let addBtn = document.createElement('input');
1348                addBtn.id = _getUniqueDomIdPrefix() + '_addAnotherButton';
1349                addBtn.setAttribute('type', 'button');
1350                addBtn.setAttribute('value', thisApi.AddContactButtonText);
1351                addBtn.addEventListener('click', function () { thisApi.AddContactRow(); });
1352
1353                additionalContactsContainer.appendChild(addBtn);
1354
1355                parentElem.appendChild(additionalContactsContainer);
1356
1357                //add first row
1358                if(thisApi.AddFirstContactRow)
1359                    thisApi.AddContactRow();
1360
1361                //wire up quanity changed event
1362                let qtyInput = thisApi.GetItemQuantityFieldElement();
1363                if (qtyInput != null) { 
1364
1365                    if(qtyInput.tagName.toLowerCase() == 'input' && qtyInput.getAttribute('type') == 'text')
1366                        qtyInput.addEventListener('input', function () { thisApi.HandleItemQuantityChanged(); });                                                      
1367                    else
1368                        qtyInput.addEventListener('change', function () { thisApi.HandleItemQuantityChanged(); });                                                      
1369
1370                }
1371
1372                thisApi.HandleItemQuantityChanged();
1373
1374                _initialized = true;
1375
1376            }
1377            else
1378                _logger.Log('invalid parent element for additional contact api (' + _getUniqueDomIdPrefix() + ')');
1379
1380        }
1381        else
1382            _logger.Log('skipping additional contacts api initialization (' + _getUniqueDomIdPrefix() + ') (already initialized)');
1383
1384        _fireInitializedEvent();
1385
1386    };
1387
1388    if (thisApi.AutoInitialize) {
1389
1390        document.addEventListener('DOMContentLoaded', function () { thisApi.Initialize(); });
1391
1392    }
1393
1394    return thisApi;
1395
1396}
1397
1398function getCheckOutItemForMeOrSomeoneElseApi(config) {
1399
1400    const thisApi = {};
1401
1402    //thisApi.Type = config.Type; //if needed (i.e., registraiton verus membership)
1403
1404    thisApi.AutoInitialize = (config.AutoInitialize != null) ? config.AutoInitialize : true;    
1405
1406    thisApi.ItemQuantityFieldDomId = config.ItemQuantityFieldDomId;
1407
1408    thisApi.RelatedElementContainerDomId = config.RelatedElementContainerDomId; 
1409
1410    thisApi.WidgetContainerDomId = config.WidgetContainerDomId;
1411
1412    thisApi.EmailAddressRequired = (config.EmailAddressRequired != null) ? config.EmailAddressRequired : false;
1413    thisApi.PhoneNumberRequired = (config.PhoneNumberRequired != null) ? config.PhoneNumberRequired : false;    
1414
1415    thisApi.ToggleLableCheckedText = (config.ToggleLableCheckedText != null) ? config.ToggleLableCheckedText : 'for me';
1416    thisApi.ToggleLableNotCheckedText = (config.ToggleLableNotCheckedText != null) ? config.ToggleLableNotCheckedText : 'someone else';
1417
1418    thisApi.RelatedElementReference_Id = (config.RelatedElementReference_Id != null) ? config.RelatedElementReference_Id : 0;
1419
1420    thisApi.HiddenFieldDomId = config.HiddenFieldDomId;    
1421
1422    const _logger = getConsoleLogApi({ "LogSourceName": 'CE Me Or Someone Else Api' });
1423
1424    thisApi.GetItemQuantityFieldElement = function () { return document.getElementById(thisApi.ItemQuantityFieldDomId); };
1425    
1426    thisApi.GetRelatedElementContainerElement = function () { return document.getElementById(thisApi.RelatedElementContainerDomId); };
1427
1428    thisApi.GetWidgetContainerElement = function () {
1429
1430        let contElem = document.getElementById(thisApi.WidgetContainerDomId);
1431
1432        if (contElem == null)
1433            contElem = thisApi.GetAddContactsWidgetElement(); //fall back (legacy forms mostly)
1434
1435        return contElem;
1436
1437    };
1438
1439    thisApi.GetAddContactsWidgetElement = function () { return document.getElementById(thisApi.RelatedElementContainerDomId + '_forMeOrSomeoneElseContainer'); };
1440
1441    thisApi.GetContactInfoContainerElement = function () { return document.getElementById(thisApi.RelatedElementContainerDomId + '_forMeOrSomeoneElseContactInfoContainer'); };
1442     
1443    thisApi.GetToggleCheckboxElement = function () { return document.getElementById(thisApi.RelatedElementContainerDomId + '_forMeOrSomeoneElseToggleCheckBox'); };
1444    thisApi.GetToggleCheckboxLabelElement = function () { return document.getElementById(thisApi.RelatedElementContainerDomId + '_forMeOrSomeoneElseToggleCheckBoxLabel'); };
1445
1446    thisApi.HandleItemQuantityChanged = function () {
1447         
1448        let wigContainerElem = thisApi.GetWidgetContainerElement();
1449        if (wigContainerElem != null) {
1450
1451            wigContainerElem.vanish();
1452
1453            let itemQuantElem = thisApi.GetItemQuantityFieldElement();
1454
1455            let itemQuant = getFormItemQuantityInputValue(itemQuantElem);
1456
1457            if (itemQuant > 0) 
1458                wigContainerElem.show();  
1459
1460        }
1461
1462    };
1463
1464    thisApi.HandleToggleCheckBoxChanged = function () {
1465
1466        let cbElem = thisApi.GetToggleCheckboxElement();
1467        let lblElem = thisApi.GetToggleCheckboxLabelElement();
1468
1469        let contactInfoContainerElem = thisApi.GetContactInfoContainerElement(); 
1470
1471        if (cbElem != null && lblElem != null && contactInfoContainerElem != null) { 
1472            
1473            if (cbElem.checked) {
1474                contactInfoContainerElem.vanish();
1475                lblElem.innerHTML = thisApi.ToggleLableCheckedText;
1476            }
1477            else
1478            { 
1479                contactInfoContainerElem.show();
1480                lblElem.innerHTML = thisApi.ToggleLableNotCheckedText;
1481            }
1482
1483        }
1484
1485    };
1486
1487    thisApi.ValidateContactInfo = function () {
1488
1489        let retVal = true;
1490
1491        let cbElem = thisApi.GetToggleCheckboxElement();
1492        let contactInfoContainer = thisApi.GetContactInfoContainerElement();      
1493
1494        //only validate if checked
1495        if (cbElem != null && !cbElem.checked && contactInfoContainer != null) { 
1496
1497            let firstNameElem = contactInfoContainer.querySelector('.forMeOrSomeoneElseContactFirstNames');
1498            firstNameElem.validationSucceeded();
1499            if (isBlank(firstNameElem.getValue())){ 
1500                retVal = false;
1501                firstNameElem.validationFailed();
1502            }
1503
1504            let lastNameElem = contactInfoContainer.querySelector('.forMeOrSomeoneElseContactLastNames');
1505            lastNameElem.validationSucceeded();
1506            if (isBlank(lastNameElem.getValue())){ 
1507                retVal = false;
1508                lastNameElem.validationFailed();
1509            }
1510
1511            let emailElem = contactInfoContainer.querySelector('.forMeOrSomeoneElseContactEmailAddresses');
1512            emailElem.validationSucceeded();
1513            if (thisApi.EmailAddressRequired && isBlank(emailElem.getValue())){ 
1514                retVal = false;
1515                emailElem.validationFailed();
1516            }
1517
1518            if (!isBlank(emailElem.getValue()) && !isValidEmailAddress(emailElem.getValue())){ 
1519                retVal = false;
1520                emailElem.validationFailed();
1521            }
1522             
1523            let phoneElem = contactInfoContainer.querySelector('.forMeOrSomeoneElseContactPhoneNumbers');
1524            phoneElem.validationSucceeded();
1525            if (thisApi.PhoneNumberRequired && isBlank(phoneElem.getValue())){ 
1526                retVal = false;
1527                phoneElem.validationFailed();
1528            }
1529
1530        }
1531
1532        return retVal;
1533
1534    }
1535
1536    thisApi.GetContactDetails = function () {
1537
1538        let retVal = {};
1539
1540        let cbElem = thisApi.GetToggleCheckboxElement();
1541        let contactInfoContainer = thisApi.GetContactInfoContainerElement();      
1542
1543        if (cbElem != null && contactInfoContainer != null) { 
1544
1545            retVal.FirstName = '';
1546            retVal.LastName = '';
1547            retVal.EmailAddress = '';
1548            retVal.PhoneNumber = '';
1549
1550            if (!cbElem.checked) { 
1551                retVal.FirstName = contactInfoContainer.querySelector('.forMeOrSomeoneElseContactFirstNames').getValue();
1552                retVal.LastName = contactInfoContainer.querySelector('.forMeOrSomeoneElseContactLastNames').getValue();
1553                retVal.EmailAddress = contactInfoContainer.querySelector('.forMeOrSomeoneElseContactEmailAddresses').getValue();
1554                retVal.PhoneNumber = contactInfoContainer.querySelector('.forMeOrSomeoneElseContactPhoneNumbers').getValue();
1555            }
1556
1557        }
1558
1559        return retVal;
1560
1561    };
1562
1563    thisApi.GetSerializedContactDetails = function () {
1564
1565        let cntArr = thisApi.GetContactDetails();
1566
1567        return JSON.stringify(cntArr);
1568
1569    };
1570
1571    thisApi.WriteSerializedContactDetailsToHiddenField = function () {
1572                
1573        if (!isBlank(thisApi.HiddenFieldDomId) && document.getElementById(thisApi.HiddenFieldDomId) != null) { 
1574
1575            let serCnt = thisApi.GetSerializedContactDetails();
1576
1577            document.getElementById(thisApi.HiddenFieldDomId).setValue(serCnt);
1578
1579        }
1580        
1581    };
1582
1583    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
1584
1585        CallBackManager.add('forMeOrSomeoneElseApiInitializedCallBack', callBackFunction, null);
1586
1587    };
1588
1589    const _fireInitializedEvent = function () {
1590
1591        let returnObj = {};
1592        returnObj.RelatedElementContainerDomId = thisApi.RelatedElementContainerDomId; //note can have multiple per page
1593
1594        CallBackManager.fire('forMeOrSomeoneElseApiInitializedCallBack', returnObj, null);
1595
1596    };
1597
1598    let _initialized = false;
1599
1600    //Initialize
1601    thisApi.Initialize = function () {
1602
1603        _logger.Log('initialize started (' + thisApi.RelatedElementContainerDomId + ')');        
1604
1605        if (!_initialized) {
1606
1607            let parentElem = document.getElementById(thisApi.WidgetContainerDomId);
1608
1609            if (parentElem == null)
1610                parentElem = thisApi.GetRelatedElementContainerElement(); //fall back (legacy forms mostly)
1611                 
1612            if (parentElem != null) {
1613                 
1614                let forMeOrSomeoneElseContainer = document.createElement('div');
1615                forMeOrSomeoneElseContainer.id = thisApi.RelatedElementContainerDomId + '_forMeOrSomeoneElseContainer';
1616                forMeOrSomeoneElseContainer.addClassName('forMeOrSomeoneElseContainers');
1617
1618                //add checkbox
1619                let cbInput = document.createElement('input');
1620                cbInput.setAttribute('type', 'checkbox');
1621                cbInput.id = thisApi.RelatedElementContainerDomId + '_forMeOrSomeoneElseToggleCheckBox';
1622                cbInput.checked = true;
1623
1624                cbInput.addEventListener('change', function () { thisApi.HandleToggleCheckBoxChanged(); });     
1625
1626                forMeOrSomeoneElseContainer.appendChild(cbInput);                
1627
1628                //add label
1629                let lbl = document.createElement('label');                
1630                lbl.id = thisApi.RelatedElementContainerDomId + '_forMeOrSomeoneElseToggleCheckBoxLabel';
1631                lbl.addClassName('forMeOrSomeoneElseToggleCheckBoxLabels');
1632                lbl.innerHTML = thisApi.ToggleLableCheckedText;
1633
1634                forMeOrSomeoneElseContainer.appendChild(lbl);  
1635
1636                let forMeOrSomeoneElseContactInfoContainer = document.createElement('div');
1637                forMeOrSomeoneElseContactInfoContainer.id = thisApi.RelatedElementContainerDomId + '_forMeOrSomeoneElseContactInfoContainer';
1638                forMeOrSomeoneElseContactInfoContainer.addClassName('forMeOrSomeoneElseContactInfoContainers');
1639                 
1640                //add first name
1641                let firstNameElem = document.createElement('input');
1642                firstNameElem.setAttribute('type', 'text');                        
1643                firstNameElem.addClassName('forMeOrSomeoneElseContactFirstNames');
1644                firstNameElem.setAttribute('placeholder', 'First Name');
1645
1646                forMeOrSomeoneElseContactInfoContainer.appendChild(firstNameElem);
1647
1648                //add last name
1649                let lastNameElem = document.createElement('input');
1650                lastNameElem.setAttribute('type', 'text');                        
1651                lastNameElem.addClassName('forMeOrSomeoneElseContactLastNames');
1652                lastNameElem.setAttribute('placeholder', 'Last Name');
1653
1654                forMeOrSomeoneElseContactInfoContainer.appendChild(lastNameElem);
1655
1656                //add email
1657                let emailElem = document.createElement('input');
1658                emailElem.setAttribute('type', 'text');                        
1659                emailElem.addClassName('forMeOrSomeoneElseContactEmailAddresses');
1660                emailElem.setAttribute('placeholder', 'Email');
1661
1662                forMeOrSomeoneElseContactInfoContainer.appendChild(emailElem);
1663
1664                //add phone
1665                let phoneElem = document.createElement('input');
1666                phoneElem.setAttribute('type', 'text');                        
1667                phoneElem.addClassName('forMeOrSomeoneElseContactPhoneNumbers');
1668                phoneElem.setAttribute('placeholder', 'Phone');
1669
1670                forMeOrSomeoneElseContactInfoContainer.appendChild(phoneElem);
1671              
1672                forMeOrSomeoneElseContainer.appendChild(forMeOrSomeoneElseContactInfoContainer);
1673                 
1674                parentElem.appendChild(forMeOrSomeoneElseContainer);
1675                  
1676                thisApi.HandleToggleCheckBoxChanged();
1677
1678                //wire up quanity changed event
1679                let qtyInput = thisApi.GetItemQuantityFieldElement();
1680                if (qtyInput != null) { 
1681
1682                    if(qtyInput.tagName.toLowerCase() == 'input' && qtyInput.getAttribute('type') == 'text')
1683                        qtyInput.addEventListener('input', function () { thisApi.HandleItemQuantityChanged(); });                                                      
1684                    else
1685                        qtyInput.addEventListener('change', function () { thisApi.HandleItemQuantityChanged(); });                                                      
1686
1687                }                                                   
1688                
1689                thisApi.HandleItemQuantityChanged();                
1690
1691                _initialized = true;
1692
1693            }
1694            else
1695                _logger.Log('invalid parent element for me or someone else api (' + thisApi.RelatedElementContainerDomId + ')');
1696
1697        }
1698        else
1699            _logger.Log('skipping for me or someone else api initialization (' + thisApi.RelatedElementContainerDomId + ') (already initialized)');
1700
1701        _fireInitializedEvent();
1702
1703    };
1704
1705    if (thisApi.AutoInitialize) {
1706
1707        document.addEventListener('DOMContentLoaded', function () { thisApi.Initialize(); });
1708
1709    }
1710
1711    return thisApi;
1712
1713}
1714
1715function getStateProvincesApi() {
1716
1717    const thisApi = {};
1718
1719    const _logger = getConsoleLogApi({ "LogSourceName": 'CE State/Provinces Api' });
1720
1721    const _isStateMetaDataAvailableInContext = function () { return (typeof stateMap != 'undefined' && stateMap != null); };
1722
1723    if (!_isStateMetaDataAvailableInContext())         
1724        _logger.LogWarning('states variable not presentat at time of initialization (ensure js file is included or variable is loaded on page when used)');
1725     
1726    const _getStateEntryById = function (state_id) {
1727
1728        let retVal = null;
1729
1730        if (state_id > 0)
1731            for (let x = 0; x < stateMap.length; x++) { 
1732
1733                for (let y = 0; y < stateMap[x].length; y++)
1734                    if (stateMap[x][y].stateID == state_id) {
1735                        retVal = stateMap[x][y];
1736                        break;
1737                    }
1738
1739                if (retVal != null)
1740                    break;
1741
1742            }
1743
1744        return retVal;
1745
1746    };
1747
1748    thisApi.GetStateAbbreviationById = function (state_id) {
1749
1750        let retVal = '';
1751
1752        let st = _getStateEntryById(state_id);
1753        if (st != null)            
1754            retVal = st.stateAbbreviation;
1755
1756        return retVal;
1757
1758    };
1759
1760    thisApi.GetStateNameById = function (state_id) {
1761
1762        let retVal = '';
1763
1764        let st = _getStateEntryById(state_id);
1765        if (st != null)  
1766            retVal = st.stateName;
1767
1768        return retVal;
1769
1770    };
1771
1772    const _getCountryStateEntries = function (country_id) {
1773
1774        let retVal = null;
1775
1776        if (country_id > 0)
1777            retVal = stateMap[country_id];
1778             
1779        return retVal;
1780
1781    };    
1782
1783    thisApi.GetCountryStateIdByNameOrAbbreviation = function (country_id, stateNameOrAbbreviation) {
1784
1785        let retVal = 0;
1786
1787        if (country_id > 0 && !isBlank(stateNameOrAbbreviation)) {
1788
1789            let cnt = _getCountryStateEntries(country_id);
1790            if (cnt != null) { 
1791
1792                for (let x = 0; x < cnt.length; x++) 
1793                    if (cnt[x].stateName == stateNameOrAbbreviation || cnt[x].stateAbbreviation == stateNameOrAbbreviation) {
1794                        retVal = cnt[x].stateID;
1795                        break;
1796                    }            
1797
1798            }
1799
1800        }
1801
1802        return retVal;
1803
1804    };
1805
1806    thisApi.RepopulateStatesDropDown = function (stateElem, country_id, addBlankOption = false) {
1807
1808        clearOptions(stateElem);
1809
1810        const cntStates = _getCountryStateEntries(country_id);
1811
1812        if (addBlankOption)
1813            stateElem.addDropDownOption(0, '', true); 
1814
1815        if (cntStates != null) { 
1816
1817            for (let x = 0; x < cntStates.length; x++)                 
1818                stateElem.addDropDownOption(cntStates[x].stateID,cntStates[x].stateAbbreviation);            
1819
1820        }
1821
1822        if (stateElem.options.length > 0)
1823            stateElem.show();
1824        else
1825            stateElem.vanish();
1826
1827    };
1828
1829    return thisApi;
1830
1831}
1832
1833function getCountriesApi() {
1834
1835    const thisApi = {};
1836
1837    const _logger = getConsoleLogApi({ "LogSourceName": 'CE Countries Api' });
1838
1839    const _isCountryMetaDataAvailableInContext = function () { return (typeof countryMap != 'undefined' && countryMap != null); };
1840
1841    if (!_isCountryMetaDataAvailableInContext())
1842        _logger.LogWarning('countries variable not present at time of initialization (ensure js file is included or variable is loaded on page when used)');     
1843
1844    thisApi.GetCountryCodeById = function (country_id) {
1845
1846        let retVal = '';
1847
1848        if (country_id > 0)
1849            retVal = countryMap[country_id].countryCode;
1850
1851        return retVal;
1852
1853    };
1854
1855    thisApi.IsPostalCodeRequiredById = function (country_id) {
1856
1857        let retVal = false;
1858
1859        if (country_id > 0)
1860            retVal = countryMap[country_id].PostalCodeRequired;
1861
1862        return retVal;
1863
1864    };
1865
1866    thisApi.GetCountryCodeByIdOrUSA = function (country_id) {
1867
1868        let retVal = thisApi.GetCountryCodeById(country_id);
1869
1870        if (isBlank(retVal))
1871            retVal = 'US';
1872
1873        return retVal;
1874
1875    };
1876
1877    thisApi.GetCountryIdByCodeOrAlias = function (countryCodeOrAlias) {
1878
1879        let retVal = 0;
1880
1881        if (!isBlank(countryCodeOrAlias)) {
1882
1883            for (let id in countryMap) 
1884                if (countryMap[id].countryCode == countryCodeOrAlias || countryMap[id].countryAlias == countryCodeOrAlias) {
1885                    retVal = id;
1886                    break;
1887                }            
1888
1889        }
1890
1891        return retVal;
1892
1893    };
1894
1895    thisApi.GetCountryIdByCodeOrAliasOrUsa = function (countryCodeOrAlias) {
1896
1897        let retVal = 0;
1898
1899        retVal = thisApi.GetCountryIdByCodeOrAlias(countryCodeOrAlias);
1900
1901        if (retVal <= 0)
1902            retVal = Countries.USA;
1903
1904        return retVal;
1905
1906    };
1907
1908    thisApi.PopulateCountriesDropDown = function (countriesElem) {
1909
1910        clearOptions(countriesElem); //paranoia
1911         
1912        if (_isCountryMetaDataAvailableInContext()) { 
1913
1914            Object.keys(countryMap).forEach(function(key) {
1915                const value = countryMap[key];
1916                countriesElem.addDropDownOption(value.countryID,value.countryName);                  
1917            });
1918             
1919        }
1920
1921        if (countriesElem.options.length > 0)
1922            countriesElem.show();
1923        else
1924            countriesElem.vanish();
1925        
1926        countriesElem.setDropDownValueOrFirst(Countries.USA); //default to USA for now
1927
1928    };
1929
1930    return thisApi;
1931
1932}
1933
1934function getFormAddressApi(config) {
1935
1936    const thisApi = {};
1937     
1938    const _autoInitialize = (config.AutoInitialize != null) ? config.AutoInitialize : true;
1939    
1940    const _required = (config.Required != null) ? config.Required : false;
1941
1942    const _applyStyles = (config.ApplyStyles != null) ? config.ApplyStyles : true;
1943    const _addBlankStateOption = (config.AddBlankStateOption != null) ? config.AddBlankStateOption : false;
1944    
1945    const _formOrContainerDomId = config.FormOrContainerDomId;
1946    
1947    const _street1FieldDomId = config.Street1FieldDomId;
1948    const _street2FieldDomId = config.Street2FieldDomId;
1949    const _cityFieldDomId = config.CityFieldDomId;
1950    const _stateProvinceFieldDomId = config.StateProvinceFieldDomId;
1951    const _postalCodeFieldDomId = config.PostalCodeFieldDomId;
1952    const _countryCodeFieldDomId = config.CountryFieldDomId;
1953    
1954    const _cityStatePostalCodeLabelDomId =  (config.CityStatePostalCodeLabelDomId != null) ? config.CityStatePostalCodeLabelDomId : null;
1955    
1956    const _logger = getConsoleLogApi({ "LogSourceName": 'CE Form Address Api' });
1957    
1958    const _getFormOrContainerElement = function () { return document.getElementById(_formOrContainerDomId); };
1959    
1960    const _getStreet1FieldElement = function () { return document.getElementById(_street1FieldDomId); };
1961    
1962    Object.defineProperty(thisApi, 'Street1FieldElement', {
1963        get: function() {
1964            return _getStreet1FieldElement();
1965        }
1966    });
1967    
1968    Object.defineProperty(thisApi, 'Street1', {
1969        get: function() {
1970            return thisApi.Street1FieldElement.getValue();
1971        },
1972        set: function(value) {                        
1973            thisApi.Street1FieldElement.setValue(value);
1974        }
1975    });
1976    
1977    const _getStreet2FieldElement = function () { return document.getElementById(_street2FieldDomId); };
1978    
1979    Object.defineProperty(thisApi, 'Street2FieldElement', {
1980        get: function() {
1981            return _getStreet2FieldElement();
1982        }
1983    });
1984    
1985    Object.defineProperty(thisApi, 'Street2', {
1986        get: function() {
1987            return thisApi.Street2FieldElement.getValue();
1988        },
1989        set: function(value) {                        
1990            thisApi.Street2FieldElement.setValue(value);
1991        }
1992    });
1993    
1994    const _getCityFieldElement = function () { return document.getElementById(_cityFieldDomId); };        
1995    
1996    Object.defineProperty(thisApi, 'CityFieldElement', {
1997        get: function() {
1998            return _getCityFieldElement();
1999        }
2000    });
2001    
2002    Object.defineProperty(thisApi, 'City', {
2003        get: function() {
2004            return thisApi.CityFieldElement.getValue();
2005        },
2006        set: function(value) {                        
2007            thisApi.CityFieldElement.setValue(value);
2008        }
2009    });
2010    
2011    const _getStateProvinceFieldElement = function () { return document.getElementById(_stateProvinceFieldDomId); };
2012    
2013    Object.defineProperty(thisApi, 'StateProvinceFieldElement', {
2014        get: function() {
2015            return _getStateProvinceFieldElement();
2016        }
2017    });
2018    
2019    Object.defineProperty(thisApi, 'StateProvince_Id', {
2020        get: function() {
2021            return thisApi.StateProvinceFieldElement.getValue().toIntZeroIfInvalid();
2022        },
2023        set: function(value) {                        
2024            thisApi.StateProvinceFieldElement.setDropDownValueOrFirst(value.toIntZeroIfInvalid());
2025        }
2026    });
2027    
2028    const _getPostalCodeFieldElement = function () { return document.getElementById(_postalCodeFieldDomId); };
2029    
2030    Object.defineProperty(thisApi, 'PostalCodeFieldElement', {
2031        get: function() {
2032            return _getPostalCodeFieldElement();
2033        }
2034    });
2035    
2036    Object.defineProperty(thisApi, 'PostalCode', {
2037        get: function() {
2038            return thisApi.PostalCodeFieldElement.getValue();
2039        },
2040        set: function(value) {                        
2041            thisApi.PostalCodeFieldElement.setValue(value);
2042        }
2043    });
2044    
2045    const _getCountryFieldElement = function () { return document.getElementById(_countryCodeFieldDomId); };
2046    
2047    Object.defineProperty(thisApi, 'CountryFieldElement', {
2048        get: function() {
2049            return _getCountryFieldElement();
2050        }
2051    });
2052    
2053    Object.defineProperty(thisApi, 'Country_Id', {
2054        get: function() {
2055            return thisApi.CountryFieldElement.getValue().toIntZeroIfInvalid();
2056        },
2057        set: function(value) {                        
2058            thisApi.CountryFieldElement.setDropDownValueOrFirst(value.toIntZeroIfInvalid());
2059        }
2060    });
2061    
2062    const _getCityStatePostalCodeLabeElement = function () {
2063    
2064        let retVal = null;
2065    
2066        if(!isBlank(_cityStatePostalCodeLabelDomId))
2067            retVal = document.getElementById(_cityStatePostalCodeLabelDomId);
2068    
2069        return retVal;
2070    
2071    };
2072    
2073    thisApi.MakeRequired = function () {
2074    
2075        _getStreet1FieldElement().setAttribute('data-validation', 'required');
2076        _getCityFieldElement().setAttribute('data-validation', 'required');
2077        _getCountryFieldElement().setAttribute('data-validation', 'required,integer,positive');
2078    
2079    };
2080    
2081    thisApi.MakeNotRequired = function () {
2082    
2083        _getStreet1FieldElement().removeAttribute('data-validation');
2084        _getCityFieldElement().removeAttribute('data-validation');
2085        _getCountryFieldElement().removeAttribute('data-validation');
2086        
2087    };
2088    
2089    const _handleCountryChange = function () {
2090    
2091        const cntId = _getCountryFieldElement().getValue().toIntZeroIfInvalid();
2092    
2093        App.StateProvinces.RepopulateStatesDropDown(_getStateProvinceFieldElement(), cntId, _addBlankStateOption);
2094    
2095        if (_required) {
2096    
2097            const postalCodeReq = App.Countries.IsPostalCodeRequiredById(cntId);
2098    
2099            if (postalCodeReq)
2100                _getPostalCodeFieldElement().setAttribute('data-validation', 'required');
2101            else
2102                _getPostalCodeFieldElement().setAttribute('data-validation', '');
2103    
2104        }
2105    
2106        if (_getCityStatePostalCodeLabeElement() != null) {
2107    
2108            //set label 
2109            let lbl = 'City';
2110    
2111            if (_getStateProvinceFieldElement().options.length > 0)
2112                lbl += ', State/Province';
2113    
2114            lbl += ', Postal Code, Country';
2115    
2116            _getCityStatePostalCodeLabeElement().innerHTML = lbl;
2117    
2118        }
2119    
2120    };
2121    
2122    let _initialized = false;
2123    
2124    //Initialize
2125    thisApi.Initialize = function () {
2126    
2127        _logger.Log('initialize started (' + _formOrContainerDomId + ')');        
2128    
2129        if (!_initialized) {
2130    
2131            if (_getFormOrContainerElement() != null) {
2132    
2133                if (_getStreet1FieldElement() != null && _getStreet2FieldElement() != null && _getCityFieldElement() != null && _getStateProvinceFieldElement() != null && _getPostalCodeFieldElement() != null && _getCountryFieldElement() != null) {
2134    
2135                    App.Countries.PopulateCountriesDropDown(_getCountryFieldElement());
2136            
2137                    _handleCountryChange();
2138    
2139                    _getCountryFieldElement().addEventListener('change', function () { _handleCountryChange(); });
2140    
2141                    //wire up validation if not present
2142                    if (_required) 
2143                        thisApi.MakeRequired();                    
2144
2145                    if (_applyStyles) {
2146                        _getStreet1FieldElement().setAttribute('maxlength', 100);
2147                        _getStreet1FieldElement().setAttribute('placeholder', 'Street Address');
2148
2149                        _getStreet2FieldElement().setAttribute('maxlength', 100);
2150                        _getStreet2FieldElement().setAttribute('placeholder', 'Suite or Unit #');
2151
2152                        _getCityFieldElement().setAttribute('maxlength', 50);
2153                        _getCityFieldElement().setAttribute('style', 'width:120px;');
2154                        _getCityFieldElement().setAttribute('placeholder', 'City');
2155
2156                        //_getStateProvinceFieldElement()                    
2157
2158                        _getPostalCodeFieldElement().setAttribute('maxlength', 20);
2159                        _getPostalCodeFieldElement().setAttribute('style', 'width:95px;');
2160                        _getPostalCodeFieldElement().setAttribute('placeholder', 'Postal Code');
2161
2162                        //_getCountryFieldElement()        
2163                    }
2164    
2165                    _initialized = true;
2166    
2167                }
2168                else
2169                    _logger.Log('invalid or missing form fields (' + _formOrContainerDomId + ')');
2170    
2171            }
2172            else
2173                _logger.Log('invalid or missing form or container element dom id (' + _formOrContainerDomId + ')');
2174    
2175        }
2176        else
2177            _logger.Log('skipping form address fields api initialization (' + _formOrContainerDomId + ') (already initialized)');
2178    
2179        //_fireInitializedEvent();
2180    
2181    };
2182    
2183    if (_autoInitialize) {
2184    
2185        document.addEventListener('DOMContentLoaded', function () { thisApi.Initialize(); });
2186    
2187    }
2188
2189    return thisApi;
2190
2191}
2192
2193function getAmountTextBoxPlusMinusButtonsApi(config) {
2194
2195    const thisApi = {};
2196
2197    thisApi.TextBoxDomId = config.TextBoxDomId;
2198    thisApi.MinimumValue = config.MinimumValue != null ? config.MinimumValue : 0;
2199    thisApi.MaximumValue = config.MaximumValue != null ? config.MaximumValue : 0;
2200
2201    const _logger = getConsoleLogApi({"LogSourceName": 'CE Amount Textbox Plus Minus Buttons Api'});
2202
2203    const _tryGetTextBoxElement = function () {return document.getElementById(thisApi.TextBoxDomId);};
2204
2205    const _tryGetAmountOrZero = function(){
2206    
2207        let retVal = 0;
2208
2209        const tbElem = _tryGetTextBoxElement();
2210        if (tbElem != null)
2211            retVal = tbElem.getValue().toIntZeroIfInvalid();
2212
2213        return retVal;
2214
2215    };
2216
2217    const _tryGetMinusButtonElement = function () {
2218
2219        let retVal = null;
2220
2221        const tbElem = _tryGetTextBoxElement();
2222        if (tbElem != null)
2223            retVal = tbElem.parentElement.querySelector('.ce-textBoxMinusButtons');
2224
2225        return retVal;
2226
2227    };
2228
2229    const _tryGetPlusButtonElement = function () {
2230
2231        let retVal = null;
2232
2233        const tbElem = _tryGetTextBoxElement();
2234        if (tbElem != null)
2235            retVal = tbElem.parentElement.querySelector('.ce-textBoxPlusButtons');
2236
2237        return retVal;
2238
2239    };
2240
2241    const _generateFormFieldButton = function (domId, clickCallbackFunction, toolTip) {
2242
2243        let retVal = document.createElement('button');
2244        retVal.id = domId;
2245        retVal.setAttribute('type', 'button');
2246
2247        if (!isBlank(toolTip))
2248            retVal.setAttribute('title', toolTip);
2249
2250        retVal.addClassName('ce-formFieldButtons ce-iconOnlyButtons ce-noThemeStyles');
2251
2252        if (typeof clickCallbackFunction == 'function')
2253            retVal.addEventListener('click', function () {clickCallbackFunction(this);});
2254
2255        let cntElem = document.createElement('span');
2256
2257        cntElem.addClassName('ce-formFieldButtonContentContainers');
2258
2259        retVal.appendChild(cntElem);
2260
2261        return retVal;
2262
2263    };
2264
2265    const _handleReduceAmountClick = function () {
2266
2267        const tbElem = _tryGetTextBoxElement();
2268        if (tbElem != null) {
2269
2270            let rawVal = tbElem.getValue();
2271            let curVal = rawVal.toIntZeroIfInvalid();
2272            //console.log('curVal: ' + curVal);            
2273
2274            if (curVal < thisApi.MinimumValue)
2275                curVal = thisApi.MinimumValue;
2276
2277            if (curVal > thisApi.MinimumValue)
2278                curVal--;
2279
2280            //console.log('rawVal: ' + rawVal);
2281            //console.log('curVal: ' + curVal);
2282
2283            if (rawVal.trim() != curVal.toString().trim()) {
2284                tbElem.setValue(curVal);
2285                //_internalHandleAmountChanged(); //TODO: figure out why addInputElementValueChangeListener isn't working and remove
2286                //_fireAmountChangedEvent();
2287            }
2288
2289        }
2290
2291    };
2292
2293    const _handleIncreaseAmountClick = function () {
2294
2295        const tbElem = _tryGetTextBoxElement();
2296        if (tbElem != null) {
2297
2298            let rawVal = tbElem.getValue();
2299            let curVal = rawVal.toIntZeroIfInvalid();
2300            //console.log('curVal: ' + curVal);
2301
2302            if (thisApi.MaximumValue <= 0 || curVal < thisApi.MaximumValue)
2303                curVal++;
2304
2305            //console.log('rawVal: ' + rawVal);
2306            //console.log('curVal: ' + curVal);
2307
2308            if (rawVal.trim() != curVal.toString().trim()) {
2309                tbElem.setValue(curVal);
2310                //_internalHandleAmountChanged(); //TODO: figure out why addInputElementValueChangeListener isn't working and remove
2311                //_fireAmountChangedEvent();
2312            }
2313
2314        }
2315
2316    };
2317
2318    thisApi.RegisterAmountChangedCallBack = function (callBackFunction) {
2319        CallBackManager.add('amountTextBoxPlusMinusButtonsApi_' + thisApi.TextBoxDomId + '_amountChangedCallBack', callBackFunction, null);
2320    };
2321
2322    const _fireAmountChangedEvent = function () {
2323        const cbParams = {"TextBoxElement": _tryGetTextBoxElement()}
2324        CallBackManager.fire('amountTextBoxPlusMinusButtonsApi_' + thisApi.TextBoxDomId + '_amountChangedCallBack', cbParams, null);
2325    };
2326
2327    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
2328        CallBackManager.add('amountTextBoxPlusMinusButtonsApi_' + thisApi.TextBoxDomId + '_initializedCallBack', callBackFunction, null);
2329    };
2330
2331    const _fireInitializedEvent = function () {
2332        CallBackManager.fire('amountTextBoxPlusMinusButtonsApi_' + thisApi.TextBoxDomId + '_initializedCallBack', {}, null);
2333    };
2334
2335    const _refreshUIWhenAmountChanges = function(){
2336    
2337        const amt = _tryGetAmountOrZero();
2338         
2339        const decreaseBtn = _tryGetMinusButtonElement();
2340         
2341        decreaseBtn.show();
2342        if (amt <= 0 || amt <= thisApi.MinimumValue) decreaseBtn.vanish();
2343
2344        const increaseBtn = _tryGetPlusButtonElement();
2345
2346        increaseBtn.show();
2347        if (thisApi.MaximumValue > 0 && amt >= thisApi.MaximumValue) increaseBtn.vanish();
2348
2349    };
2350
2351    const _internalHandleAmountChanged = function () {
2352
2353        _refreshUIWhenAmountChanges();
2354
2355        _fireAmountChangedEvent();
2356    
2357    };
2358
2359    //Initialize
2360    thisApi.Initialize = function () {
2361
2362        _logger.Log('initialize started (' + thisApi.TextBoxDomId + ')');
2363
2364        const tbElem = _tryGetTextBoxElement();
2365
2366        if (tbElem == null) {
2367            _logger.Log('invalid textbox element (abort initialization)');
2368            return;
2369        }
2370
2371        const minusBtnElem = _tryGetMinusButtonElement();
2372
2373        if (minusBtnElem != null) {
2374            _logger.Log('minus button already present (abort initialization)');
2375            return;
2376        }
2377
2378        const plusBtnElem = _tryGetPlusButtonElement();
2379
2380        if (plusBtnElem != null) {
2381            _logger.Log('plus button already present (abort initialization)');
2382            return;
2383        }
2384
2385        const tbElemContainer = tbElem.parentElement;
2386
2387        if (tbElemContainer == null) {
2388            _logger.Log('invalid textbox parent element (abort initialization)');
2389            return;
2390        }
2391
2392        tbElem.addClassName('ce-amountWithPlusMinusButtonInputs');
2393        tbElem.parentElement.addClassName('ce-amountWithPlusMinusButtonInputContainers');
2394
2395        const buttonsWrapperElem = document.createElement('div');
2396        buttonsWrapperElem.addClassName('ce-adjacentFormFieldButtonContainers');
2397
2398        //add minus button
2399        const tikMinusButton = _generateFormFieldButton(thisApi.TextBoxDomId + '_reduceAmountButton', function () {_handleReduceAmountClick();}, 'reduce amount');
2400        tikMinusButton.addClassName('ce-textBoxMinusButtons');
2401        tikMinusButton.firstElementChild.addClassNames('fa fa-minus');
2402        //tikMinusButton.setAttribute('data-textboxDomId', thisApi.TextBoxDomId);
2403        buttonsWrapperElem.appendChild(tikMinusButton);
2404
2405        const tikPlusButton = _generateFormFieldButton(thisApi.TextBoxDomId + '_increaseAmountButton', function () {_handleIncreaseAmountClick();}, 'increase amount');
2406        tikPlusButton.addClassName('ce-textBoxPlusButtons');
2407        tikPlusButton.firstElementChild.addClassNames('fa fa-plus');
2408        //tikPlusButton.setAttribute('data-textboxDomId', thisApi.TextBoxDomId);
2409        buttonsWrapperElem.appendChild(tikPlusButton);
2410
2411        tbElemContainer.appendChild(buttonsWrapperElem);
2412
2413        addInputElementValueChangeListener(tbElem, function () { _internalHandleAmountChanged(); });
2414
2415        if (typeof config.AmountChangedCallBack == 'function')
2416            thisApi.RegisterAmountChangedCallBack(config.AmountChangedCallBack);
2417
2418        _refreshUIWhenAmountChanges();
2419
2420        _fireInitializedEvent();
2421
2422    };
2423
2424    return thisApi;
2425
2426}
2427
2428function getInKindTypesApi() {
2429
2430    const thisApi = {};
2431
2432    const _logger = getConsoleLogApi({ "LogSourceName": 'CE In-Kind Types Api' });
2433
2434    const _inKindTypeDataAvailableInContext = (typeof WebFormContext != 'undefined' && WebFormContext.InKindTypeClassMap != 'undefined');
2435
2436    if (!_inKindTypeDataAvailableInContext)         
2437        _logger.LogWarning('InKindTypeClassMap variable not present (ensure variable is published on page)');     
2438
2439    thisApi.GetClassIdByTypeId = function (type_id) {
2440
2441        let retVal = '';
2442
2443        if (type_id > 0)
2444            retVal = WebFormContext.InKindTypeClassMap[type_id];
2445
2446        return retVal;
2447
2448    };
2449     
2450    return thisApi;
2451
2452}
2453
2454function getCurrencyApi() {
2455
2456    const thisApi = {};
2457
2458    const _logger = getConsoleLogApi({ "LogSourceName": 'CE Currency Api' });
2459
2460    const _currenciesAvailableInContext = (typeof currencies != 'undefined');
2461
2462    if (!_currenciesAvailableInContext)
2463        _logger.LogWarning('currencies variable not present (ensure js file is included or variable is published on page)');            
2464
2465    thisApi.GetCurrencyIdByCode = function(currencyCode){
2466
2467        let retVal = 0;
2468         
2469        if (!isBlank(currencyCode)) {
2470
2471            for (let x = 0; x < currencies.length; x++)
2472                if (currencies[x].CurrencyCode == currencyCode) {
2473                    retVal = currencies[x].Id;
2474                    break;
2475                }
2476
2477        }
2478 
2479        return retVal;
2480
2481    }
2482
2483    thisApi.GetCurrencyIdByCodeOrUSD = function(currencyCode){
2484
2485        let retVal = thisApi.GetCurrencyIdByCode(currencyCode);
2486
2487        if (retVal <= 0)
2488            retVal = Currencies.USD;
2489             
2490        return retVal;
2491
2492    }
2493
2494    thisApi.GetCurrencyCodeByIdOrUSD = function (currency_id) {
2495
2496        let retVal = '';
2497
2498        if (currency_id <= 0)
2499            currency_id = Currencies.USD;
2500             
2501        for (let x = 0; x < currencies.length; x++)
2502            if (currencies[x].Id == currency_id) {
2503                retVal = currencies[x].CurrencyCode;
2504                break;
2505            }
2506 
2507        return retVal;
2508
2509    };
2510
2511    thisApi.GetCurrencySymbolByIdOrUSD = function (currency_id) {
2512
2513        let retVal = '';
2514
2515        if (currency_id <= 0)
2516            currency_id = Currencies.USD;
2517
2518        for (let x = 0; x < currencies.length; x++)
2519            if (currencies[x].Id == currency_id) {
2520                retVal = currencies[x].Symbol;
2521                break;
2522            }
2523 
2524        return retVal;
2525
2526    };
2527
2528    return thisApi;
2529
2530}
2531
2532function getSingleLineAddressFormFieldApi(config) {
2533
2534    const thisApi = {};
2535
2536    thisApi.AutoInitialize = (config.AutoInitialize != null) ? config.AutoInitialize : true;   
2537
2538    //Two use cases: eitehr map dom element and API will set values or leave them unnassigned and listen for the validation successed event to implement custom logic
2539    thisApi.SingleLineAddressTextBoxDomId = config.SingleLineAddressTextBoxDomId;
2540    thisApi.AddressStreet1TextBoxDomId = config.AddressStreet1TextBoxDomId;
2541    thisApi.AddressStreet2TextBoxDomId = config.AddressStreet2TextBoxDomId;
2542    thisApi.AddressCityTextBoxDomId = config.AddressCityTextBoxDomId;
2543    thisApi.AddressStateProvinceDropDownDomId = config.AddressStateProvinceDropDownDomId;    
2544    thisApi.AddressPostalCodeTextBoxDomId = config.AddressPostalCodeTextBoxDomId;
2545    thisApi.AddressCountryDropDownDomId = config.AddressCountryDropDownDomId;
2546    //thisApi.PlaceAutocompleteElement = config.PlaceAutocompleteElement; //New GOOGLE Maps API
2547
2548    //TODO: add show/hide element for error toggle?
2549
2550    const _logger = getConsoleLogApi({ "LogSourceName": 'CE Single Line Address Form Field API (' + thisApi.SingleLineAddressTextBoxDomId + ')' });
2551
2552    const _getSingleLineAddressTextBoxElement = function () { return document.getElementById(thisApi.SingleLineAddressTextBoxDomId); };
2553
2554    Object.defineProperty(thisApi, 'SingleLineAddressTextBox', {
2555        get: function () {
2556            return _getSingleLineAddressTextBoxElement();
2557        }
2558    });
2559
2560    const _getAddressStreet1TextBoxElement = function () { return document.getElementById(thisApi.AddressStreet1TextBoxDomId); };
2561
2562    Object.defineProperty(thisApi, 'AddressStreet1TextBoxElement', {
2563        get: function () {
2564            return _getAddressStreet1TextBoxElement();
2565        }
2566    });
2567
2568    const _getAddressStreet2TextBoxElement = function () { return document.getElementById(thisApi.AddressStreet2TextBoxDomId); };
2569
2570    Object.defineProperty(thisApi, 'AddressStreet2TextBoxElement', {
2571        get: function () {
2572            return _getAddressStreet2TextBoxElement();
2573        }
2574    });
2575
2576    const _getAddressCityTextBoxElement = function () { return document.getElementById(thisApi.AddressCityTextBoxDomId); };
2577
2578    Object.defineProperty(thisApi, 'AddressCityTextBoxElement', {
2579        get: function () {
2580            return _getAddressCityTextBoxElement();
2581        }
2582    });
2583
2584    const _getAddressStateProvinceDropDownElement = function () { return document.getElementById(thisApi.AddressStateProvinceDropDownDomId); };
2585
2586    Object.defineProperty(thisApi, 'AddressStateProvinceDropDownElement', {
2587        get: function () {
2588            return _getAddressStateProvinceDropDownElement();
2589        }
2590    });
2591     
2592    const _getAddressPostalCodeTextBoxElement = function () { return document.getElementById(thisApi.AddressPostalCodeTextBoxDomId); };
2593
2594    Object.defineProperty(thisApi, 'AddressPostalCodeTextBoxElement', {
2595        get: function () {
2596            return _getAddressPostalCodeTextBoxElement();
2597        }
2598    });
2599     
2600    const _getAddressCountryDropDownElement = function () { return document.getElementById(thisApi.AddressCountryDropDownDomId); };
2601
2602    Object.defineProperty(thisApi, 'AddressCountryDropDownElement', {
2603        get: function () {
2604            return _getAddressCountryDropDownElement();
2605        }
2606    });
2607
2608    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
2609        CallBackManager.add('singleLineAddressFormField_' + thisApi.PostalCodeEntryTextBoxDomId + '_initializedCallBack', callBackFunction, null);
2610    };
2611
2612    const _fireInitializedEvent = function () {
2613        CallBackManager.fire('singleLineAddressFormField_' + thisApi.PostalCodeEntryTextBoxDomId + '_initializedCallBack', {}, null);
2614    };
2615
2616    thisApi.RegisterAddressSelectedCallBack = function (callBackFunction) {
2617        CallBackManager.add('singleLineAddressFormField_' + thisApi.PostalCodeEntryTextBoxDomId + '_addressSelectedCallBack', callBackFunction, null);
2618    };
2619
2620    const _fireAddressSelectedEvent = function (result) {
2621
2622        let cb = {};
2623        cb.Result = result;
2624
2625        CallBackManager.fire('singleLineAddressFormField_' + thisApi.PostalCodeEntryTextBoxDomId + '_addressSelectedCallBack', cb, null);
2626
2627    };
2628
2629    thisApi.RegisterGoogleMapsIntegrationFailureCallBack = function (callBackFunction) {
2630        CallBackManager.add('singleLineAddressFormField_' + thisApi.PostalCodeEntryTextBoxDomId + '_GoogleMapsIntegrationFailureCallBack', callBackFunction, null);
2631    };
2632
2633    const _fireGoogleMapsIntegrationFailureEvent = function () {
2634        CallBackManager.fire('singleLineAddressFormField_' + thisApi.PostalCodeEntryTextBoxDomId + '_GoogleMapsIntegrationFailureCallBack', {}, null);
2635    };
2636
2637    //thisApi.RegisterAddressLookupFailedCallBack = function (callBackFunction) {
2638    //    CallBackManager.add('singleLineAddressFormField_' + thisApi.PostalCodeEntryTextBoxDomId + '_addressLookupFailedCallBack', callBackFunction, null);
2639    //};
2640    //
2641    //const _fireAddressLookupFailedEvent = function () {        
2642    //    CallBackManager.fire('singleLineAddressFormField_' + thisApi.PostalCodeEntryTextBoxDomId + '_addressLookupFailedCallBack', {}, null);
2643    //};
2644
2645    const _handleSingleLineAddressSelected = function (selectedPlace) {
2646
2647        //comment out following line for new Google Maps API
2648        selectedPlace = _googleAutoCompleteApi.getPlace();      
2649
2650        _logger.Log('single line address selected ' + selectedPlace.formatted_address);
2651
2652        //_logger.Log('single line address selected ' + selectedPlace.formattedAddress); //New Google Maps API
2653
2654        let street1Formatted = parseGoogleAddressLine1Short(selectedPlace);
2655        let city = parseGoogleCityShortName(selectedPlace);
2656        let state = parseGoogleStateShortName(selectedPlace);
2657        let zip = parseGooglePostalCodeLongName(selectedPlace);
2658        let country = parseGoogleCountryShortName(selectedPlace);
2659
2660        //_logger.Log('street1Formatted: ' + street1Formatted);
2661        //_logger.Log('city: ' + city);
2662        //_logger.Log('state: ' + state);
2663        //_logger.Log('zip: ' + zip);
2664        //_logger.Log('country: ' + country);
2665
2666        //todo: wire up
2667        if (thisApi.AddressStreet1TextBoxElement != null)
2668            thisApi.AddressStreet1TextBoxElement.setValue(street1Formatted);
2669
2670        //if (thisApi.AddressStreet2TextBoxElement != null)
2671        //    thisApi.AddressStreet2TextBoxElement.setValue('');
2672
2673        if (thisApi.AddressCityTextBoxElement != null)
2674            thisApi.AddressCityTextBoxElement.setValue(city);
2675
2676        if (thisApi.AddressStateProvinceDropDownElement != null)
2677            thisApi.AddressStateProvinceDropDownElement.setValue('');
2678
2679        if (thisApi.AddressPostalCodeTextBoxElement != null)
2680            thisApi.AddressPostalCodeTextBoxElement.setValue(zip);
2681
2682        if (thisApi.AddressCountryDropDownElement != null)
2683            thisApi.AddressCountryDropDownElement.setValue('');
2684
2685        //todo: add show/hide toggle error handler
2686
2687        _fireAddressSelectedEvent(selectedPlace);
2688
2689    };
2690
2691    const _printGoogleMapsSetupInstructions = function () {
2692
2693        console.log(
2694            '%cGoogle Maps Address Lookup — Setup Required',
2695            'color: #d93025; font-weight: bold; font-size: 14px;'
2696        );
2697
2698        console.log(
2699            '%cYour Google Maps address lookup is not working. Follow these steps to fix it:',
2700            'font-weight: bold;'
2701        );
2702
2703        console.log('1. Go to the Google Cloud Console: https://console.cloud.google.com/');
2704        console.log('2. Sign in with the account that owns your API key.');
2705        console.log('3. Make sure the correct project is selected in the top dropdown.');
2706        console.log('4. In the left menu, go to APIs & Services -> Library.');
2707        console.log('5. Search for "Places API" (not "Places API (New)") and click Enable.');
2708        console.log('6. Confirm billing is enabled for the project under the Billing section.');
2709        console.log('7. Wait 2-5 minutes, then refresh this page.');
2710
2711        console.log(
2712            '%cIf it still does not work, also check:',
2713            'font-weight: bold;'
2714        );
2715
2716        console.log('- API key restrictions: under APIs & Services -> Credentials, confirm "Places API" is allowed.');
2717        console.log('- Website restrictions: confirm your domain is listed under HTTP referrer restrictions.');
2718
2719    };
2720
2721    const _handleGoogleMapsIntegrationFailure = function (reasonMessage) {
2722
2723        let genMessage = 'Google Maps integration failed';
2724
2725        if (!isBlank(reasonMessage))
2726            genMessage += '(' + reasonMessage + ')';
2727
2728        console.error(genMessage);
2729
2730        _printGoogleMapsSetupInstructions();
2731
2732        _fireGoogleMapsIntegrationFailureEvent();
2733
2734    };
2735
2736    let _googleAutoCompleteApi = null;
2737
2738    let _initialized = false;
2739
2740    //Initialize
2741    thisApi.Initialize = function () {
2742
2743        _logger.Log('initialize started');        
2744
2745        if (!_initialized) {
2746             
2747            if (thisApi.SingleLineAddressTextBoxDomId != null) {  
2748
2749                //LEGACY GOOGLE MAPS API
2750                 
2751                // 1. Guard against the API not being loaded at all
2752                if (typeof google != 'undefined' && google.maps && google.maps.places) { 
2753                                                    
2754                    // 2. Catch synchronous construction errors
2755                    try {
2756
2757                        _googleAutoCompleteApi = new google.maps.places.Autocomplete(thisApi.SingleLineAddressTextBox, { types: ['address'], fields: ['formatted_address', 'geometry', 'address_components'] });
2758
2759                    }
2760                    catch (constructionError)
2761                    {
2762                        _handleGoogleMapsIntegrationFailure(constructionError.message);                
2763                    }
2764
2765                    // 3. Wrap the listener callback
2766                    _googleAutoCompleteApi.addListener('place_changed',
2767                        function () {
2768
2769                            try {
2770                                _handleSingleLineAddressSelected();
2771                            }
2772                            catch (handlerError)
2773                            {
2774                                _handleGoogleMapsIntegrationFailure(handlerError.message);
2775                            }
2776                             
2777                        });
2778
2779                }
2780                else
2781                    _handleGoogleMapsIntegrationFailure('Google Maps Places library did not load.');    
2782
2783                //LEGACY GOOGLE MAPS API
2784
2785                /*
2786                New Goggle Maps API
2787                _googleAutoCompleteApi = thisApi.PlaceAutocompleteElement;
2788
2789                _googleAutoCompleteApi.addEventListener('gmp-select', async (event) => {
2790                    const place = event.placePrediction.toPlace();
2791
2792                    await place.fetchFields({
2793                        fields: ['formattedAddress', 'location', 'addressComponents', 'displayName']
2794                    });
2795
2796                    _handleSingleLineAddressSelected(place);
2797
2798                });
2799
2800                //legacy fallback
2801                _googleAutoCompleteApi.addEventListener('gmp-placeselect', async (event) => {
2802                    const place = event.placePrediction.toPlace();
2803                    console.log("Selected Place:", place);
2804                });*/
2805                 
2806                _initialized = true;
2807
2808            }
2809            else
2810                _logger.Log('missing single line address field for postal code form field api');
2811
2812        }
2813        else
2814            _logger.Log('skipping single line address form field api initialization (already initialized)');
2815
2816        _fireInitializedEvent();
2817
2818    };
2819
2820    if (thisApi.AutoInitialize) {
2821
2822        document.addEventListener('DOMContentLoaded', function () { thisApi.Initialize(); });
2823
2824    }
2825
2826    return thisApi;
2827
2828}
2829
2830function getPostalCodeLookupFormFieldApi(config) {
2831
2832    const thisApi = {};
2833
2834    thisApi.AutoInitialize = (config.AutoInitialize != null) ? config.AutoInitialize : true;   
2835
2836    //Two use cases: eitehr map dom elemeent and API will set values or leave them unnassigned and listen for the validation successed event to implement custom logic
2837    thisApi.PostalCodeEntryTextBoxDomId = config.PostalCodeEntryTextBoxDomId;
2838    thisApi.StateProvinceDropDownDomId = config.StateProvinceDropDownDomId;
2839    thisApi.CityTextBoxDomId = config.CityTextBoxDomId;
2840    thisApi.CountryDropDownDomId = config.CountryDropDownDomId;
2841
2842    const _logger = getConsoleLogApi({ "LogSourceName": 'CE Postal Code Form Field API' });
2843
2844    const _getPostalCodeEntryTextBoxElement = function () { return document.getElementById(thisApi.PostalCodeEntryTextBoxDomId); };
2845
2846    Object.defineProperty(thisApi, 'PostalCodeEntryTextBoxElement', {
2847        get: function () {
2848            return _getPostalCodeEntryTextBoxElement();
2849        }
2850    });
2851
2852    const _getStateProvinceDropDownElement = function () { return document.getElementById(thisApi.StateProvinceDropDownDomId); };
2853
2854    Object.defineProperty(thisApi, 'StateProvinceDropDownElement', {
2855        get: function () {
2856            return _getStateProvinceDropDownElement();
2857        }
2858    });
2859
2860    const _getCityTextBoxElement = function () { return document.getElementById(thisApi.CityTextBoxDomId); };
2861
2862    Object.defineProperty(thisApi, 'CityTextBoxElement', {
2863        get: function () {
2864            return _getCityTextBoxElement();
2865        }
2866    });
2867
2868    const _getCountryDropDownElement = function () { return document.getElementById(thisApi.CountryDropDownDomId); };
2869
2870    Object.defineProperty(thisApi, 'CountryDropDownElement', {
2871        get: function () {
2872            return _getCountryDropDownElement();
2873        }
2874    });
2875
2876    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
2877        CallBackManager.add('postalCodeLookupFormField_' + thisApi.PostalCodeEntryTextBoxDomId + '_initializedCallBack', callBackFunction, null);
2878    };
2879
2880    const _fireInitializedEvent = function () {
2881        CallBackManager.fire('postalCodeLookupFormField_' + thisApi.PostalCodeEntryTextBoxDomId + '_initializedCallBack', {}, null);
2882    };
2883
2884    thisApi.RegisterLookupSuccessfulCallBack = function (callBackFunction) {
2885        CallBackManager.add('postalCodeLookupFormField_' + thisApi.PostalCodeEntryTextBoxDomId + '_lookupSuccessfulCallBack', callBackFunction, null);
2886    };
2887
2888    const _fireLookupSuccessfulEvent = function (result) {
2889
2890        let cb = {};
2891        cb.Result = result;
2892
2893        CallBackManager.fire('postalCodeLookupFormField_' + thisApi.PostalCodeEntryTextBoxDomId + '_lookupSuccessfulCallBack', cb, null);
2894
2895    };
2896
2897    thisApi.RegisterLookupFailedCallBack = function (callBackFunction) {
2898        CallBackManager.add('postalCodeLookupFormField_' + thisApi.PostalCodeEntryTextBoxDomId + '_lookupFailedCallBack', callBackFunction, null);
2899    };
2900
2901    const _fireLookupFailedEvent = function () {        
2902        CallBackManager.fire('postalCodeLookupFormField_' + thisApi.PostalCodeEntryTextBoxDomId + '_lookupFailedCallBack', {}, null);
2903    };
2904
2905    const _lookupPostalCode = function (successCallBack, errorCallBack) {
2906
2907        let postalCode = thisApi.PostalCodeEntryTextBoxElement.getValue();
2908         
2909        LookupPostalCodeLocationInfo(postalCode, 
2910            function (result) {                
2911                 
2912                if (result.Success) {
2913
2914                    if(thisApi.CityTextBoxElement != null && thisApi.CityTextBoxElement.getValue() != result.City)
2915                        thisApi.CityTextBoxElement.setValue(result.City);
2916
2917                    if (thisApi.StateProvinceDropDownElement != null && thisApi.StateProvinceDropDownElement.getValue() != result.State_Id)
2918                        thisApi.StateProvinceDropDownElement.setValue(result.State_Id);                    
2919
2920                    if (thisApi.CountryDropDownElement != null && thisApi.CountryDropDownElement.getValue() != result.Country_Id)
2921                        thisApi.StateProvinceDropDownElement.setValue(result.Country_Id);
2922                         
2923                    if (typeof successCallBack == 'function')
2924                        successCallBack();
2925
2926                    _fireLookupSuccessfulEvent(result);
2927
2928                }
2929                else {
2930
2931                    _logger.Log('error: ' + result.ErrorMessage);
2932                 
2933                    if (typeof errorCallBack == 'function')
2934                        errorCallBack();
2935
2936                    _fireLookupFailedEvent();
2937
2938                }
2939 
2940            },
2941            function (error) {
2942
2943                _logger.Log('error: ' + error);
2944                 
2945                if (typeof errorCallBack == 'function')
2946                    errorCallBack();
2947
2948                _fireLookupFailedEvent();
2949
2950            });
2951             
2952    };
2953
2954    let _initialized = false;
2955
2956    //Initialize
2957    thisApi.Initialize = function () {
2958
2959        _logger.Log('initialize started (' + thisApi.PostalCodeEntryTextBoxDomId + ')');        
2960
2961        if (!_initialized) {
2962             
2963            if (thisApi.PostalCodeEntryTextBoxElement != null) { // && thisApi.StateProvinceDropDownElement != null && thisApi.CityTextBoxElement
2964                
2965                thisApi.PostalCodeEntryTextBoxElement.addEventListener('blur', function () { _lookupPostalCode() });                                     
2966                 
2967                _initialized = true;
2968
2969            }
2970            else
2971                _logger.Log('missing postal code entry field for postal code form field api (' + thisApi.PostalCodeEntryTextBoxDomId + ')');
2972
2973        }
2974        else
2975            _logger.Log('skipping postal code form field api initialization (' + thisApi.PostalCodeEntryTextBoxDomId + ') (already initialized)');
2976
2977        _fireInitializedEvent();
2978
2979    };
2980
2981    if (thisApi.AutoInitialize) {
2982
2983        document.addEventListener('DOMContentLoaded', function () { thisApi.Initialize(); });
2984
2985    }
2986
2987    return thisApi;
2988
2989}
2990
2991function getPromoCodeFormFieldApi(config) {
2992
2993    const thisApi = {};
2994
2995    thisApi.AutoInitialize = (config.AutoInitialize != null) ? config.AutoInitialize : true;   
2996
2997    thisApi.PromoEntryTextBoxDomId = config.PromoEntryTextBoxDomId;
2998
2999    thisApi.GetTotalAmountFunction = config.GetTotalAmountFunction;
3000    thisApi.GetSelectedTicketQuantitiesFunction = config.GetSelectedTicketQuantitiesFunction;
3001
3002    thisApi.ValidatePromoButtonDomId = (config.ValidatePromoButtonDomId != null) ? config.ValidatePromoButtonDomId : null;   
3003
3004    thisApi.Form_Id = (config.Form_Id != null) ? config.Form_Id : 0;
3005
3006    const _logger = getConsoleLogApi({ "LogSourceName": 'CE Promo Code Api' });
3007
3008    thisApi.ValidateResponse = null;
3009
3010    thisApi.GetPromoEntryTextBoxElement = function () { return document.getElementById(thisApi.PromoEntryTextBoxDomId); };
3011
3012    Object.defineProperty(thisApi, 'PromoEntryTextBoxElement', {
3013        get: function () {
3014            return thisApi.GetPromoEntryTextBoxElement();
3015        }
3016    });
3017
3018    thisApi.GetValidatePromoButtonElement = function () { return document.getElementById(thisApi.ValidatePromoButtonDomId); };
3019
3020    Object.defineProperty(thisApi, 'ValidatePromoButtonElement', {
3021        get: function () {
3022            return thisApi.GetValidatePromoButtonElement();
3023        }
3024    });
3025
3026    thisApi.GetValidatePromoMessageContainerElement = function () {
3027
3028        let retVal = null;
3029
3030        if (thisApi.ValidatePromoButtonElement != null)
3031            retVal = thisApi.ValidatePromoButtonElement.parentElement.querySelector('.validatePromoCodeResultMessages');
3032
3033        return retVal;
3034
3035    };
3036
3037    Object.defineProperty(thisApi, 'ValidatePromoMessageContainerElement', {
3038        get: function () {
3039            return thisApi.GetValidatePromoMessageContainerElement();
3040        }
3041    });
3042
3043    const _clearMessages = function () {
3044
3045        if (thisApi.ValidatePromoMessageContainerElement != null) { 
3046            thisApi.ValidatePromoMessageContainerElement.removeClassName('error');
3047            thisApi.ValidatePromoMessageContainerElement.removeClassName('success');
3048            thisApi.ValidatePromoMessageContainerElement.innerText = '';
3049            thisApi.ValidatePromoMessageContainerElement.vanish();
3050        }
3051
3052    };
3053
3054    const _setSuccessMessage = function (message) {
3055
3056        _clearMessages();
3057
3058        if (thisApi.ValidatePromoMessageContainerElement != null) { 
3059            thisApi.ValidatePromoMessageContainerElement.addClassName('success');
3060            thisApi.ValidatePromoMessageContainerElement.innerText = message;
3061            thisApi.ValidatePromoMessageContainerElement.show();
3062        }
3063
3064    };
3065
3066    const _setErrorMessage = function (message) {
3067
3068        _clearMessages();
3069
3070        if (thisApi.ValidatePromoMessageContainerElement != null) { 
3071            thisApi.ValidatePromoMessageContainerElement.addClassName('error');
3072            thisApi.ValidatePromoMessageContainerElement.innerText = message;
3073            thisApi.ValidatePromoMessageContainerElement.show();
3074        }
3075
3076    };
3077
3078    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
3079        CallBackManager.add('promo_' + thisApi.PromoEntryTextBoxDomId + '_initializedCallBack', callBackFunction, null);
3080    };
3081
3082    const _fireInitializedEvent = function () {
3083        CallBackManager.fire('promo_' + thisApi.PromoEntryTextBoxDomId + '_initializedCallBack', {}, null);
3084    };
3085
3086    thisApi.RegisterValidationSuccessfulChangedCallBack = function (callBackFunction) {
3087        CallBackManager.add('promo_' + thisApi.PromoEntryTextBoxDomId + '_validationSuccessfulCallBack', callBackFunction, null);
3088    };
3089
3090    const _fireValidationSuccessfulEvent = function () {
3091        CallBackManager.fire('promo_' + thisApi.PromoEntryTextBoxDomId + '_validationSuccessfulCallBack', {}, null);
3092    };
3093
3094    thisApi.RegisterValidationFailedChangedCallBack = function (callBackFunction) {
3095        CallBackManager.add('promo_' + thisApi.PromoEntryTextBoxDomId + '_validationFailedCallBack', callBackFunction, null);
3096    };
3097
3098    const _fireValidationFailedEvent = function () {
3099        CallBackManager.fire('promo_' + thisApi.PromoEntryTextBoxDomId + '_validationFailedCallBack', {}, null);
3100    };
3101
3102    thisApi.ValidateResponse = null;
3103
3104    thisApi.GetDiscountAmount = function () {
3105
3106        let retVal = 0;
3107
3108        if (thisApi.ValidateResponse != null && thisApi.ValidateResponse.Success)
3109            retVal = thisApi.ValidateResponse.DiscountAmount;
3110
3111        return retVal;
3112
3113    };
3114
3115    thisApi.GetTicketsDiscountAmount = function () {
3116
3117        let retVal = 0;
3118
3119        if (thisApi.ValidateResponse != null && thisApi.ValidateResponse.Success)
3120            retVal = thisApi.ValidateResponse.TicketsAppliedDiscount;
3121
3122        return retVal;
3123
3124    };
3125
3126    thisApi.GetNonTicketDiscountAmount = function () {
3127
3128        let retVal = 0;
3129
3130        if (thisApi.ValidateResponse != null && thisApi.ValidateResponse.Success)
3131            retVal = thisApi.ValidateResponse.NonTicketAppliedDiscount;
3132
3133        return retVal;
3134
3135    };
3136
3137    thisApi.Validate = function (successCallBack, errorCallBack) { _validateCode(successCallBack, errorCallBack); };
3138
3139    const _validateCode = function (successCallBack, errorCallBack) {
3140
3141        let promoCode = thisApi.PromoEntryTextBoxElement.getValue();
3142        let amt = thisApi.GetTotalAmountFunction();
3143        let tix = thisApi.GetSelectedTicketQuantitiesFunction();
3144
3145        _clearMessages();
3146
3147        thisApi.PromoEntryTextBoxElement.validationSucceeded();
3148
3149        ValidatePromoCode(promoCode, amt, tix, thisApi.Form_Id,
3150            function (result) {                
3151
3152                thisApi.ValidateResponse = result;
3153
3154                if (result.Success)
3155                    _setSuccessMessage('promo code applied');
3156                else
3157                {  
3158                    thisApi.PromoEntryTextBoxElement.validationFailed(); 
3159                    _setErrorMessage(result.ErrorMessage);
3160                }
3161
3162                if (typeof successCallBack == 'function')
3163                    successCallBack();
3164
3165                _fireValidationSuccessfulEvent();
3166
3167            },
3168            function (error) {
3169
3170                _logger.Log('error: ' + error);
3171
3172                thisApi.PromoEntryTextBoxElement.validationFailed();
3173
3174                if (typeof errorCallBack == 'function')
3175                    errorCallBack();
3176
3177                _fireValidationFailedEvent();
3178
3179            });
3180             
3181    };
3182
3183    let _initialized = false;
3184
3185    //Initialize
3186    thisApi.Initialize = function () {
3187
3188        _logger.Log('initialize started (' + thisApi.PromoEntryTextBoxDomId + ')');        
3189
3190        if (!_initialized) {
3191             
3192            if (thisApi.PromoEntryTextBoxElement != null) {
3193
3194                if (thisApi.ValidatePromoButtonElement == null) {
3195
3196                    thisApi.PromoEntryTextBoxElement.setAttribute('placeholder','enter promo code');
3197
3198                    thisApi.ValidatePromoButtonDomId = thisApi.PromoEntryTextBoxDomId + '_validateBtn';
3199                     
3200                    let btn = document.createElement('button');
3201                    btn.setAttribute('type', 'button');
3202                    btn.id = thisApi.ValidatePromoButtonDomId;
3203                    btn.innerHTML = 'apply';
3204                    btn.addEventListener('click', function () { _validateCode() });
3205                     
3206                    thisApi.PromoEntryTextBoxElement.insertAdjacentElement('afterend', btn); 
3207
3208                    let messCont = document.createElement('span');
3209                    messCont.addClassName('validatePromoCodeResultMessages');
3210
3211                    messCont.vanish();
3212                     
3213                    btn.insertAdjacentElement('afterend', messCont); 
3214                     
3215                }
3216                 
3217                _initialized = true;
3218
3219            }
3220            else
3221                _logger.Log('invalid text box element for promo code form field api (' + thisApi.PromoEntryTextBoxDomId + ')');
3222
3223        }
3224        else
3225            _logger.Log('skipping promo code form field api initialization (' + thisApi.PromoEntryTextBoxDomId + ') (already initialized)');
3226
3227        _fireInitializedEvent();
3228
3229    };
3230
3231    if (thisApi.AutoInitialize) {
3232
3233        document.addEventListener('DOMContentLoaded', function () { thisApi.Initialize(); });
3234
3235    }
3236
3237    return thisApi;
3238
3239}
3240
3241function getLiveActivityStreamWidgetApi(config) {
3242
3243    const thisApi = {};
3244
3245    thisApi.MainContainerElementDomId = config.MainContainerElementDomId;
3246
3247    if(config.Title != null)
3248        thisApi.Title = config.Title;
3249
3250    thisApi.MaxItems = 50;
3251
3252    if (config != null && config.MaxItems > 0)
3253        thisApi.MaxItems = config.MaxItems;
3254             
3255    const _logger = getConsoleLogApi({ "LogSourceName": 'CE Live Activity Stream Widget Api' });
3256
3257    const _getMainContainerElement = function () { return document.getElementById(thisApi.MainContainerElementDomId); };
3258         
3259    const _seenIds = {};
3260
3261    const _avatarMap = {};
3262
3263    const _avatars = [
3264        { bg: '#e1f5ee', text: '#0f6e56' },
3265        { bg: '#e6f1fb', text: '#185fa5' },
3266        { bg: '#eeedfe', text: '#3c3489' },
3267        { bg: '#faeeda', text: '#854f0b' },
3268        { bg: '#faece7', text: '#993c1d' },
3269        { bg: '#fbeaf0', text: '#993556' },
3270    ];
3271
3272    const _avatarFor = function (name) {
3273
3274        if (!_avatarMap[name]) {
3275            var i = Object.keys(_avatarMap).length % _avatars.length;
3276            _avatarMap[name] = _avatars[i];
3277        }
3278
3279        return _avatarMap[name];
3280
3281    };
3282
3283    const _initials = function(name) {
3284        const parts = name.trim().split(/\s+/);
3285        return parts.length >= 2 ? parts[0][0] + parts[parts.length - 1][0] : name.slice(0, 2);
3286    }
3287
3288    const _escHtml = function (s) {
3289        return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
3290    };
3291
3292    const _timeLabel = function () {
3293        const now = new Date();
3294        const h = now.getHours(), m = now.getMinutes();
3295        const ampm = h >= 12 ? 'pm' : 'am';
3296        h = h % 12 || 12;
3297        return 'Today at ' + h + ':' + (m < 10 ? '0' : '') + m + ' ' + ampm;
3298    };
3299
3300    const _addFeedItem = function(item) {
3301
3302        if (item.id) {
3303            if (_seenIds[item.id]) return;
3304            _seenIds[item.id] = true;
3305        }
3306
3307        const list = document.getElementById('liveActivityFeedPanelListContainer');
3308        const empty = document.getElementById('liveActivityFeedPanelListFeedEmptyEntry');
3309        if (empty) empty.remove();
3310
3311        const av = _avatarFor(item.name);
3312        const li = document.createElement('li');
3313        li.className = 'feed-item';
3314
3315        const amountHtml = item.amount
3316            ? ' <span class="feed-amount">
3316$' + Number(item.amount).toLocaleString() + '</span>'
3317            : '';
3318
3319        li.innerHTML =
3320            '<div class="feed-avatar" style="background:' + av.bg + ';color:' + av.text + '">' +
3321            _initials(item.name) +
3322            '</div>' +
3323            '<div class="feed-body">' +
3324            '<div class="feed-text"><strong>' + _escHtml(item.name) + '</strong> ' + _escHtml(item.text) + amountHtml + '</div>' +
3325            '<div class="feed-time">' + _escHtml(item.time || _timeLabel()) + '</div>' +
3326            '</div>';
3327
3328        list.insertBefore(li, list.firstChild);
3329
3330        const items = list.querySelectorAll('.feed-item');
3331        if (items.length > thisApi.MaxItems) 
3332            list.removeChild(items[items.length - 1]);
3333
3334    }
3335
3336    thisApi.AddItem = function(item){ _addFeedItem(item); };
3337
3338    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
3339        CallBackManager.add('liveActivityStreamWidgetApiInitializedCallBack', callBackFunction, null);
3340    };
3341
3342    let _fireInitializedEvent = function () {
3343        CallBackManager.fire('liveActivityStreamWidgetApiInitializedCallBack', {}, null);
3344    };
3345         
3346    //Initialize
3347    thisApi.Initialize = function () {
3348
3349        if (document.getElementById('liveActivityFeedPanel') != null) {
3350            _logger.Log('initialize completed (abort)');
3351            return;
3352        }
3353
3354        _logger.Log('initialize started');
3355
3356        const mainContainerElement = _getMainContainerElement();
3357        if (mainContainerElement != null) {
3358
3359            const widgetContainerElem = document.createElement('div');
3360            widgetContainerElem.id = 'liveActivityFeedPanel';
3361
3362            const headerElem = document.createElement('div');
3363            headerElem.id = 'liveActivityFeedPanelHeader'; 
3364
3365            const headerTitleElem = document.createElement('span');
3366            headerTitleElem.id = 'liveActivityFeedPanelTitle';
3367            headerTitleElem.innerHTML = thisApi.Title;
3368
3369            headerElem.appendChild(headerTitleElem);
3370
3371            const headerLiveElem = document.createElement('span');
3372            headerLiveElem.id = 'liveActivityFeedPanelTopRightLiveCallOut';
3373
3374            const headerDotElem = document.createElement('span');
3375            headerDotElem.id = 'liveActivityFeedPanelTopRightLiveDot';
3376            headerLiveElem.appendChild(headerDotElem);
3377
3378            headerLiveElem.appendChild(document.createTextNode(' Live'));
3379
3380            headerElem.appendChild(headerLiveElem);
3381
3382            widgetContainerElem.appendChild(headerElem);
3383
3384            const feedContainerElem = document.createElement('div');
3385            feedContainerElem.id = 'liveActivityFeedPanelListContainer'; 
3386
3387            const waitContElem = document.createElement('li');
3388            waitContElem.id = 'liveActivityFeedPanelListFeedEmptyEntry';
3389            waitContElem.innerHTML = 'Waiting for activity...';
3390
3391            feedContainerElem.appendChild(waitContElem);
3392
3393            widgetContainerElem.appendChild(feedContainerElem);
3394
3395            mainContainerElement.appendChild(widgetContainerElem);                     
3396
3397            _fireInitializedEvent();
3398
3399        }
3400        else
3401            _logger.Log('unable to initialize Live Activity Stream Widget becuase no container is available');
3402 
3403    };
3404
3405    return thisApi;
3406
3407}
3408
3409function getThermometerWidgetApi(config) {
3410
3411    const thisApi = {};
3412
3413    thisApi.MainContainerElementDomId = config.MainContainerElementDomId;
3414
3415    if(config.Title != null)
3416        thisApi.Title = config.Title;
3417
3418    if(config.SubTitle != null)
3419        thisApi.SubTitle = config.SubTitle;
3420    
3421    let _meterFillHexColor = '7DC242'; //TODO: allow configuration
3422
3423    let _goalRevenue = 0;
3424    let _amountRaised = 0;    
3425
3426    const _logger = getConsoleLogApi({ "LogSourceName": 'CE Thermometer Widget Api' });
3427
3428    const _getMainContainerElement = function () { return document.getElementById(thisApi.MainContainerElementDomId); };
3429     
3430    const _refreshTitle = function () {
3431
3432        const titleElem = document.getElementById('thermometerPanelTitle');
3433        if (titleElem != null) {
3434
3435            titleElem.innerHTML = thisApi.Title;
3436
3437            //always show for now due to page layout - TBD
3438            //if (!isBlank(thisApi.Title))
3439            //    titleElem.show();
3440            //else
3441            //    titleElem.vanish();
3442
3443        }
3444
3445    };
3446
3447    thisApi.SetTitle = function (value) { thisApi.Title = value; _refreshTitle(); };
3448
3449    const _refreshSubTitle = function () {
3450
3451        const subTitleElem = document.getElementById('thermometerPanelSubTitle');
3452        if (subTitleElem != null) {
3453
3454            subTitleElem.innerHTML = thisApi.SubTitle;
3455
3456            //always show for now due to page layout - TBD
3457            //if (!isBlank(thisApi.SubTitle))
3458            //    subTitleElem.show();
3459            //else
3460            //    subTitleElem.vanish();
3461
3462        }
3463
3464    };
3465
3466    thisApi.SetSubTitle = function (value) { thisApi.SubTitle = value; _refreshSubTitle(); };
3467
3468    thisApi.SetGoal = function (amt,suppressRefresh) { _goalRevenue = amt; if(!suppressRefresh) _refreshThermometerSvg(); };
3469     
3470    thisApi.SetAmount = function (amt,suppressRefresh) { _amountRaised = amt; if(!suppressRefresh) _refreshThermometerSvg(); };
3471
3472    const _TX = 31;
3473    const _TW = 28;
3474    const _TT = 20;
3475    const _TH = 280;
3476    const _TB = 300;
3477    const _BCY = 332;
3478    const _BR = 24;
3479
3480    const _fmt = function (n) {
3481        if (n >= 1000000) return '$' + (n / 1000000).toFixed(n % 1000000 === 0 ? 0 : 1) + 'M';
3482        if (n >= 10000) return '$' + (n / 1000).toFixed(n % 1000 === 0 ? 0 : 1) + 'K';
3483        return '$' + Math.round(n).toLocaleString();
3484    };
3485
3486    const _fmtFull = function (n) { return '$' + Math.round(n).toLocaleString(); };
3487
3488    //revisit
3489    //const _generateMeterColorByPercent = function (p) {
3490    //    if (p >= 1.0) return '#1D9E75';
3491    //    if (p >= 0.75) return '#378ADD';
3492    //    if (p >= 0.50) return '#7F77DD';
3493    //    if (p >= 0.25) return '#BA7517';
3494    //    return '#D85A30';
3495    //};
3496
3497    function _refreshThermometerSvg() {
3498
3499        const goal = _goalRevenue;
3500        const raised = _amountRaised;
3501
3502        const pct = goal > 0 ? Math.min(raised / goal, 1) : 0;
3503
3504        const col = '#' + _meterFillHexColor; // _generateMeterColorByPercent(pct);
3505
3506        const pi = Math.round(pct * 100);
3507
3508        document.getElementById('thermometerTopStatRaisedAmount').textContent = _fmtFull(raised);
3509        document.getElementById('thermometerTopStatGoalAmount').textContent = _fmtFull(goal);
3510        document.getElementById('thermometerTopStatRemainingAmount').textContent = _fmtFull(Math.max(goal - raised, 0));
3511
3512        const footCallOut = document.getElementById('thermometerFooter');
3513        footCallOut.textContent = pct >= 1 ? '🎉 Goal reached!' : pi + '% of goal reached';
3514        footCallOut.className = (pct >= 1 ? 'done' : '');
3515
3516        const fh = pct * _TH;
3517        const fy = _TB - fh;
3518
3519        let svg = '<defs><clipPath id="tc"><rect x="' + _TX + '" y="' + _TT + '" width="' + _TW + '" height="' + _TH + '" rx="14"/></clipPath></defs>';
3520        svg += '<rect x="' + _TX + '" y="' + _TT + '" width="' + _TW + '" height="' + _TH + '" rx="14" fill="#f0efe9" stroke="#d8d7d0" stroke-width="1"/>';
3521        svg += '<circle cx="45" cy="' + _BCY + '" r="' + _BR + '" fill="#f0efe9" stroke="#d8d7d0" stroke-width="1"/>';
3522         
3523        if (fh > 0)
3524            svg += '<rect x="' + _TX + '" y="' + fy + '" width="' + _TW + '" height="' + (fh + 2) + '" clip-path="url(#tc)" fill="' + col + '"/>';
3525
3526        svg += '<circle cx="45" cy="' + _BCY + '" r="' + (_BR - 5) + '" fill="' + col + '"/>';
3527
3528        [0.25, 0.5, 0.75, 1.0].forEach(function (m) {
3529
3530            const my = _TB - m * _TH;
3531            const hit = pct >= m - 0.001;
3532            const full = m === 1.0;
3533
3534            svg += '<line x1="' + (_TX - 4) + '" y1="' + my + '" x2="' + (_TX + _TW + 4) + '" y2="' + my + '" stroke="' + (hit ? col : '#ccc') + '" stroke-width="' + (full ? 2 : 1) + '" stroke-dasharray="' + (full ? '' : '3,2') + '"/>';
3535            svg += '<text x="' + (_TX + _TW + 8) + '" y="' + (my + 4) + '" font-size="9" font-family="monospace" fill="' + (hit ? col : '#aaa') + '">' + Math.round(m * 100) + '%</text>';
3536
3537        });
3538
3539        if (pct > 0.07)
3540            svg += '<text x="45" y="' + Math.max(fy + 14, _TT + 14) + '" text-anchor="middle" font-size="8" font-family="monospace" font-weight="500" fill="white">' + pi + '%</text>';
3541
3542        document.getElementById('thermometer').innerHTML = svg;
3543
3544        const labelsEl = document.getElementById('thermometerLeftLabelsContainer');
3545        labelsEl.innerHTML = '';
3546        [goal, goal * 0.75, goal * 0.5, goal * 0.25, 0].forEach(function (v) {
3547            const d = document.createElement('div');
3548            d.className = 'thermo-label';
3549            d.textContent = _fmt(Math.round(v));
3550            labelsEl.appendChild(d);
3551        });
3552
3553        //hide goal dependent UI
3554        if (goal > 0) {
3555
3556            document.getElementById('thermometerTopStatGoalAmount').parentElement.show();
3557            document.getElementById('thermometerTopStatRemainingAmount').parentElement.show();
3558            document.getElementById('thermometerContainer').show();
3559            document.getElementById('thermometerFooter').show();
3560
3561        }
3562        else {
3563
3564            document.getElementById('thermometerTopStatGoalAmount').parentElement.vanish();
3565            document.getElementById('thermometerTopStatRemainingAmount').parentElement.vanish();
3566            document.getElementById('thermometerContainer').vanish();
3567            document.getElementById('thermometerFooter').vanish();
3568
3569        }
3570
3571    }
3572
3573    thisApi.Reload = function () { _refreshThermometerSvg(); };
3574
3575    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
3576        CallBackManager.add('thermometerWidgetApiInitializedCallBack', callBackFunction, null);
3577    };
3578
3579    let _fireInitializedEvent = function () {
3580        CallBackManager.fire('thermometerWidgetApiInitializedCallBack', {}, null);
3581    };
3582         
3583    const _addTopKpiElem = function (containerElem, domId, label) {
3584
3585        const kpiContainerElem = document.createElement('div');
3586        kpiContainerElem.addClassName('thermo-top-stat');
3587
3588        const lblElem = document.createElement('div');
3589        lblElem.addClassName('thermo-top-stat-lbl');
3590        lblElem.innerHTML = label //todo: make configurable
3591
3592        kpiContainerElem.appendChild(lblElem);
3593
3594        const valElem = document.createElement('div');
3595        valElem.id = domId;
3596        valElem.addClassName('thermo-top-stat-val');                
3597
3598        kpiContainerElem.appendChild(valElem);
3599
3600        containerElem.appendChild(kpiContainerElem);
3601
3602    };
3603
3604    //Initialize
3605    thisApi.Initialize = function () {
3606            
3607        if (document.getElementById('thermometerPanel') != null) {
3608            _logger.Log('initialize completed (abort)');
3609            return;
3610        }
3611
3612        _logger.Log('initialize started');
3613
3614        const mainContainerElement = _getMainContainerElement();
3615        if (mainContainerElement != null) {
3616
3617            const thermContainerElem = document.createElement('div');
3618            thermContainerElem.id = 'thermometerPanel';
3619
3620            const titleElem = document.createElement('div');
3621            titleElem.id = 'thermometerPanelTitle';
3622            titleElem.innerHTML = '...';
3623
3624            thermContainerElem.appendChild(titleElem);
3625
3626            const subTitleElem = document.createElement('div');
3627            subTitleElem.id = 'thermometerPanelSubTitle';
3628            subTitleElem.innerHTML = '...';
3629
3630            thermContainerElem.appendChild(subTitleElem);
3631
3632            const topStatsContainerElem = document.createElement('div');
3633            topStatsContainerElem.id = 'thermometerPanelTopStatsContainer';
3634
3635            _addTopKpiElem(topStatsContainerElem, 'thermometerTopStatRaisedAmount', 'Raised'); //todo: make label configurable
3636            _addTopKpiElem(topStatsContainerElem, 'thermometerTopStatGoalAmount', 'Goal'); //todo: make label configurable
3637            _addTopKpiElem(topStatsContainerElem, 'thermometerTopStatRemainingAmount', 'Remaining'); //todo: make label configurable
3638                 
3639            thermContainerElem.appendChild(topStatsContainerElem);
3640
3641            const thermometerContainerElem = document.createElement('div');
3642            thermometerContainerElem.id = 'thermometerContainer';
3643
3644            const termLeftContElem = document.createElement('div');
3645            termLeftContElem.id = 'thermometerLeftLabelsContainer';
3646            thermometerContainerElem.appendChild(termLeftContElem);
3647
3648            const svgElem = createSvgElement();
3649            svgElem.id = 'thermometer';
3650            svgElem.setAttribute('width', '90');
3651            svgElem.setAttribute('height', '360');                    
3652            svgElem.setAttribute('viewBox', '0 0 90 360');
3653            //svgElem.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
3654            thermometerContainerElem.appendChild(svgElem);
3655
3656            thermContainerElem.appendChild(thermometerContainerElem);
3657
3658            const thermometerFooterElem = document.createElement('div');
3659            thermometerFooterElem.id = 'thermometerFooter';
3660
3661            thermContainerElem.appendChild(thermometerFooterElem);
3662
3663            mainContainerElement.appendChild(thermContainerElem);                     
3664
3665            //start init UI api calls
3666            thisApi.SetTitle(thisApi.Title);
3667            thisApi.SetSubTitle(thisApi.SubTitle);
3668
3669            if(config.Goal != null && config.Goal > 0)
3670                thisApi.SetGoal(config.Goal, true);
3671
3672            if(config.Amount != null && config.Amount > 0)
3673                thisApi.SetAmount(config.Amount, true);
3674
3675            _refreshThermometerSvg();
3676
3677            _fireInitializedEvent();
3678
3679        }
3680        else
3681            _logger.Log('unable to initialize Thermometer Widget Api becuase no container is available');
3682 
3683    };
3684
3685    return thisApi;
3686
3687}
3688
3689function getRequestValidationApi(config) {
3690
3691    const thisApi = {};
3692
3693    thisApi.CurrentRequestToken = config.CurrentRequestToken;
3694
3695    thisApi.HeaderKeyName = config.HeaderKeyName;
3696    thisApi.FormHiddenFieldId = config.FormHiddenFieldId;
3697    thisApi.HoneypotHeaderKeyName = config.HoneypotHeaderKeyName;
3698    thisApi.HoneypotFormHiddenFieldId = config.HoneypotFormHiddenFieldId;
3699
3700    let _refreshProcessing = false;
3701
3702    const _logger = getConsoleLogApi({ "LogSourceName": 'CE Request Validation Api' });
3703
3704    const _refreshToken = function (successCallBack, errorCallBack) {        
3705
3706        let isTabVisible = true;
3707
3708        //extra defensive in case older browsers do not support
3709        try {
3710            isTabVisible = (document.visibilityState == "visible");
3711        }
3712        catch (err) {
3713            _logger.Log('check tab visiblity failed: ' + err);
3714        }
3715
3716        if (isTabVisible) {
3717
3718            _logger.Log('refresh token called');
3719
3720            if (!_refreshProcessing) {
3721
3722                _refreshProcessing = true;
3723
3724                //showProcessingMessage(); //removed (6.4.2026)
3725
3726                try {
3727
3728                    getUrlWithSessionAsync('/api/PublicWebAppUIAjaxEndpoint/GenerateRequestToken',
3729                        function (result) {
3730
3731                            //hideProcessingMessage(); //removed (6.4.2026)
3732
3733                            _setToken(result);
3734
3735                            _refreshProcessing = false;
3736
3737                            if (typeof successCallBack == 'function')
3738                                successCallBack();
3739
3740                        },
3741                        function (error) {
3742
3743                            //hideProcessingMessage(); //removed (6.4.2026)
3744
3745                            _logger.Log('error: ' + error);
3746
3747                            _refreshProcessing = false;
3748
3749                            if (typeof errorCallBack == 'function')
3750                                errorCallBack();
3751
3752                        });
3753
3754                }
3755                catch (err) {
3756
3757                    _logger.Log('request validation api unable to refresh request token (check endpoint and client APIs): ' + err);
3758
3759                    _refreshProcessing = false;
3760
3761                    if (typeof errorCallBack == 'function')
3762                        errorCallBack();
3763
3764                }
3765
3766            }
3767            else
3768            { 
3769
3770                _logger.Log('refresh token request skipped due to active refresh processing');
3771
3772                if (typeof errorCallBack == 'function')
3773                    errorCallBack();
3774
3775            }
3776
3777        }
3778        else
3779        { 
3780
3781            _logger.Log('skipping refresh becuase tab is not visible');        
3782
3783            if (typeof errorCallBack == 'function')
3784                errorCallBack();
3785
3786        }
3787
3788    };
3789
3790    thisApi.RefreshToken = function (successCallBack, errorCallBack) { _refreshToken(successCallBack, errorCallBack); };
3791
3792    const _setToken = function (token) {
3793
3794        thisApi.CurrentRequestToken = token;
3795
3796        let currentHiddenField = document.getElementById(thisApi.FormHiddenFieldId);
3797        if (currentHiddenField != null)
3798            currentHiddenField.setValue(token);
3799
3800        _logger.Log('request token updated'); // to ' + token);
3801
3802    };
3803
3804    const _getHoneyPotValue = function () {
3805
3806        let retVal = '';
3807
3808        let currentHiddenField = document.getElementById(thisApi.HoneypotFormHiddenFieldId);
3809        if (currentHiddenField != null)
3810            retVal = currentHiddenField.value;
3811
3812        return retVal;
3813
3814    };
3815
3816    thisApi.GetHoneyPotValue = function () { return _getHoneyPotValue(); };
3817
3818    //Initialize
3819    thisApi.Initialize = function () {
3820
3821        _logger.Log('initialize started');        
3822
3823        //find form to append hidden field to
3824        let formElem = getDotNetPostbackFormOrFirstFormElement();
3825
3826        //inject hidden field into DOM for form post based behavior
3827        let currentHiddenField = document.getElementById(thisApi.FormHiddenFieldId);
3828        if (currentHiddenField == null && formElem != null) {
3829
3830            //base validation field
3831            let hf = document.createElement('input');
3832            hf.setAttribute('type', 'hidden');
3833            hf.setAttribute('id', thisApi.FormHiddenFieldId);
3834            hf.setAttribute('name', thisApi.FormHiddenFieldId);
3835            hf.setValue(thisApi.CurrentRequestToken);
3836
3837            formElem.appendChild(hf);
3838 
3839        } 
3840
3841        //add honeypot field
3842        let hpFieldElem = document.getElementById(thisApi.HoneypotFormHiddenFieldId);
3843        if (hpFieldElem == null) {
3844
3845            //browsers ignore autocomplete no for some fields. In order to stop the browser from auto-populating our honeypot field, create a second field for the browser to populate instead
3846            let hp_hf_un = document.createElement('input');
3847            hp_hf_un.setAttribute('type', 'text');
3848            hp_hf_un.setAttribute('id', 'ahp_username');
3849            hp_hf_un.setAttribute('name', 'ahp_username');
3850            hp_hf_un.setAttribute('class', 'coreContactUserNameEntryFields');
3851            hp_hf_un.setAttribute('tabindex', '-1');
3852            hp_hf_un.setAttribute('autocomplete', 'yes');
3853            hp_hf_un.setAttribute('style', 'display:none;');
3854
3855            //always add honeypot, even if there is no dot net form                
3856            if (formElem != null)
3857                formElem.appendChild(hp_hf_un);
3858            else
3859                document.body.appendChild(hp_hf_un);
3860
3861            let hp_hf = document.createElement('input');
3862            hp_hf.setAttribute('type', 'text');
3863            hp_hf.setAttribute('id', thisApi.HoneypotFormHiddenFieldId);
3864            hp_hf.setAttribute('name', thisApi.HoneypotFormHiddenFieldId);
3865            hp_hf.setAttribute('class', 'coreContactEmailEntryFields');
3866            hp_hf.setAttribute('tabindex', '-1');
3867            hp_hf.setAttribute('autocomplete', 'off');
3868            hp_hf.setAttribute('style', 'display:none;');
3869
3870            //always add honeypot, even if there is no dot net form                
3871            if (formElem != null)
3872                formElem.appendChild(hp_hf);
3873            else
3874                document.body.appendChild(hp_hf);
3875
3876        }
3877
3878        //ajax behavior will rely on headers
3879
3880        // Refresh token every 5 minutes
3881        setInterval(function () { _refreshToken(); }, (300 * 1000));
3882
3883        // Add the visibility listener
3884        try {
3885
3886            document.addEventListener('visibilitychange', function () {
3887                if (document.visibilityState === 'visible') {
3888                    _logger.Log('Tab became visible, refreshing token immediately...');
3889                    _refreshToken();
3890                }
3891            });
3892
3893        } catch (err) {
3894            _logger.Log('visibility listener failed: ' + err);
3895        }
3896
3897    };
3898
3899    return thisApi;
3900
3901}
3902
3903function getGoogleCaptchaApi(config) {
3904
3905    const thisApi = {};
3906
3907    thisApi.GoogleCaptchaApiKey = config.GoogleCaptchaApiKey;
3908    thisApi.GoogleCaptchaAvailable = false;
3909
3910    thisApi.HeaderKeyName = config.HeaderKeyName;
3911    thisApi.FormHiddenFieldId = config.FormHiddenFieldId;
3912
3913    thisApi.CurrentCaptchaToken = '';
3914
3915    const _logger = getConsoleLogApi({ "LogSourceName": 'CE Captcha Api' });
3916
3917    //Events
3918    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
3919        CallBackManager.add('googleCaptchaInitializedCallBack', callBackFunction, null);
3920    };
3921
3922    const _fireInitializedEvent = function () {
3923        CallBackManager.fire('googleCaptchaInitializedCallBack', {}, null);
3924    };
3925
3926    let _captchaRefreshing = false;
3927
3928    const _refreshCaptchaToken = function (successCallBack, errorCallBack) {
3929
3930        if (thisApi.GoogleCaptchaAvailable) {
3931
3932            if (!_captchaRefreshing) {
3933
3934                _captchaRefreshing = true;
3935
3936                grecaptcha.execute(thisApi.GoogleCaptchaApiKey, { action: "submit" }).then(function (token) {
3937
3938                    _logger.Log('captcha token refreshed'); //: ' + token);
3939
3940                    _captchaRefreshing = false;
3941
3942                    thisApi.CurrentCaptchaToken = token;
3943
3944                    let currentHiddenField = document.getElementById(thisApi.FormHiddenFieldId);
3945                    if (currentHiddenField != null)
3946                        currentHiddenField.setValue(token);
3947
3948                    if (typeof successCallBack == 'function')
3949                        successCallBack();
3950
3951                }).catch(function (error) {
3952
3953                    console.error('error refreshing CAPTCHA:', error);
3954
3955                    _captchaRefreshing = false;
3956
3957                    if (typeof errorCallBack == 'function')
3958                        errorCallBack();
3959
3960                });
3961
3962                //not supported by google api
3963                //.finally(function () {
3964                //
3965                //    _logger.Log('CAPTCHA token request finished.'); // This will run no matter what (whether success or failure)
3966                //
3967                //    _captchaRefreshing = false;
3968                //
3969                //    if (typeof anyCallBack == 'function')
3970                //        anyCallBack();
3971                //
3972                //});
3973
3974            }
3975            else {
3976
3977                _logger.Log('CAPTCHA token request skipped (already running)');
3978
3979                if (typeof errorCallBack == 'function')
3980                    errorCallBack();
3981
3982            }
3983
3984        }
3985        else
3986        { 
3987
3988            _logger.Log('cannot refresh Google Captcha token becuase API is not available');
3989
3990            if (typeof errorCallBack == 'function')
3991                errorCallBack();
3992
3993        }
3994
3995    };
3996
3997    thisApi.RefreshCaptchaToken = function (successCallBack, errorCallBack) { _refreshCaptchaToken(successCallBack, errorCallBack); };
3998
3999    thisApi.HidePageIcon = function () {
4000
4001        const pgIconElem = document.querySelector('.grecaptcha-badge');
4002        if (pgIconElem != null)
4003            pgIconElem.vanish();
4004
4005    };
4006
4007    //Initialize
4008    thisApi.Initialize = function () {
4009
4010        _logger.Log('initialize started');
4011
4012        //_logger.Log('try init captcha: ' + thisApi.GoogleCaptchaApiKey);
4013        if (!isBlank(thisApi.GoogleCaptchaApiKey)) {
4014
4015            if (grecaptcha) {
4016
4017                thisApi.GoogleCaptchaAvailable = true;
4018
4019                //inject hidden field into DOM for form post based behavior
4020                let currentHiddenField = document.getElementById(thisApi.FormHiddenFieldId);
4021                if (currentHiddenField == null) {
4022
4023                    //find form to append hidden field to
4024                    let formElem = getDotNetPostbackFormOrFirstFormElement();
4025
4026                    if (formElem != null) {
4027
4028                        //base validation field
4029                        let hf = document.createElement('input');
4030                        hf.setAttribute('type', 'hidden');
4031                        hf.setAttribute('id', thisApi.FormHiddenFieldId);
4032                        hf.setAttribute('name', thisApi.FormHiddenFieldId);
4033                        //hf.setValue('');            
4034
4035                        formElem.appendChild(hf);
4036
4037                    }
4038
4039                }
4040                 
4041                // Refresh token every 90 seconds (tokens expire every 2 mintutes)
4042                setInterval(function () { _refreshCaptchaToken(); }, (90 * 1000));
4043
4044                _refreshCaptchaToken(function () { _fireInitializedEvent(); }, function () { _fireInitializedEvent(); });
4045                
4046            }
4047            else
4048                _logger.Log('unable to initialize Google Captcha API becuase no API key available');
4049
4050        }
4051        else
4052            _logger.Log('unable to initialize Google Captcha API becuase no API key available');
4053
4054    };
4055
4056    return thisApi;
4057
4058}
4059 
4060function getOneClickCheckOutApi(config) {
4061
4062    const thisApi = {};
4063
4064    thisApi.AutoInitialize = (config.AutoInitialize != null) ? config.AutoInitialize : true;   
4065     
4066    thisApi.ParentElementDomId = config.ParentElementDomId;
4067    thisApi.AddAfterElementDomId = config.AddAfterElementDomId;
4068    
4069    thisApi.Contact_Id = config.Contact_Id;
4070    thisApi.PaymentProfile_Id = config.PaymentProfile_Id;
4071    thisApi.PaymentProfileDescription = config.PaymentProfileDescription;
4072
4073    thisApi.FormRowCssClass = config.FormRowCssClass;
4074
4075    const _logger = getConsoleLogApi({ "LogSourceName": 'CE One Click Check Out API' });
4076
4077    let _oneClickCheckOutSelectedByUser = false;  
4078
4079    const _getParentElement = function () { return document.getElementById(thisApi.ParentElementDomId); };
4080
4081    Object.defineProperty(thisApi, 'ParentElement', {
4082        get: function () {
4083            return _getParentElement();
4084        }
4085    });
4086
4087    const _getAddAfterElement = function () { return document.getElementById(thisApi.AddAfterElementDomId); };
4088
4089    Object.defineProperty(thisApi, 'AddAfterElement', {
4090        get: function () {
4091            return _getAddAfterElement();
4092        }
4093    });
4094     
4095    Object.defineProperty(thisApi, 'EnabledByUser', {
4096        get: function () {
4097            return _oneClickCheckOutSelectedByUser;
4098        }
4099    });
4100
4101    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
4102        CallBackManager.add('oneClickCheckOutInitializedCallBack', callBackFunction, null);
4103    };
4104
4105    const _fireInitializedEvent = function () {
4106        CallBackManager.fire('oneClickCheckOutInitializedCallBack', {}, null);
4107    };
4108
4109    thisApi.RegisterHideAllOptionsCallBack = function (callBackFunction) {
4110        CallBackManager.add('oneClickCheckOutHideAllOptionsCallBack', callBackFunction, null);
4111    };
4112
4113    const _fireHideAllOptionsEvent = function () {
4114        CallBackManager.fire('oneClickCheckOutHideAllOptionsCallBack', {}, null);
4115    };
4116
4117    thisApi.RegisterDefaultStateShownCallBack = function (callBackFunction) {
4118        CallBackManager.add('oneClickCheckOutDefaultStateShownCallBack', callBackFunction, null);
4119    };
4120
4121    const _fireDefaultStateShownEvent = function () {
4122        CallBackManager.fire('oneClickCheckOutDefaultStateShownCallBack', {}, null);
4123    };
4124
4125    thisApi.RegisterUseAccountSelectedCallBack = function (callBackFunction) {
4126        CallBackManager.add('oneClickCheckOutUseAccountSelectedCallBack', callBackFunction, null);
4127    };
4128
4129    const _fireUseAccountSelectedEvent = function () {
4130        CallBackManager.fire('oneClickCheckOutUseAccountSelectedCallBack', {}, null);
4131    };
4132
4133    thisApi.RegisterDontUseCallBack = function (callBackFunction) {
4134        CallBackManager.add('oneClickCheckOutDontUseCallBack', callBackFunction, null);
4135    };
4136
4137    const _fireDontUseEvent = function () {
4138        CallBackManager.fire('oneClickCheckOutDontUseCallBack', {}, null);
4139    };
4140
4141    thisApi.RegisterForgetInfoCallBack = function (callBackFunction) {
4142        CallBackManager.add('oneClickCheckOutForgetInfoCallBack', callBackFunction, null);
4143    };
4144
4145    const _fireForgetInfoEvent = function () {
4146        CallBackManager.fire('oneClickCheckOutForgetInfoCallBack', {}, null);
4147    };
4148
4149    thisApi.RegisterDisabledCallBack = function (callBackFunction) {
4150        CallBackManager.add('oneClickCheckOutDisableCallBack', callBackFunction, null);
4151    };
4152
4153    const _fireDisabledEvent = function () {
4154        CallBackManager.fire('oneClickCheckOutDisableCallBack', {}, null);
4155    };
4156
4157    const _setOneClickHideAll = function () {
4158
4159        if (_initialized) {
4160             
4161            document.getElementById('oneClickTopRow').vanish();
4162
4163            document.getElementById('oneClickCheckoutMessagesContainer').vanish();
4164            document.getElementById('oneClickCheckoutInitialQuestion').vanish();
4165            document.getElementById('oneClickCheckoutAllSetMessage').vanish();
4166            document.getElementById('oneClickCheckoutUndoUseButton').vanish();
4167
4168            document.getElementById('oneClickBottomRow').vanish();
4169
4170            document.getElementById('oneClickCheckoutContainer').vanish();
4171            document.getElementById('oneClickCheckoutUseProfileButton').vanish();
4172            document.getElementById('oneClickCheckoutDoNotUseButton').vanish();
4173            document.getElementById('oneClickCheckoutForgetButton').vanish();
4174
4175            _fireHideAllOptionsEvent();
4176             
4177        }
4178
4179    };
4180
4181    const _setOneClickDefaultState = function () {
4182
4183        if (_initialized) {
4184
4185            _setOneClickHideAll();
4186
4187            _oneClickCheckOutSelectedByUser = false;            
4188
4189            document.getElementById('oneClickTopRow').show();
4190
4191            document.getElementById('oneClickCheckoutMessagesContainer').show();
4192            document.getElementById('oneClickCheckoutInitialQuestion').show();
4193
4194            document.getElementById('oneClickBottomRow').show();
4195
4196            document.getElementById('oneClickCheckoutContainer').show();
4197            document.getElementById('oneClickCheckoutUseProfileButton').show();
4198            document.getElementById('oneClickCheckoutDoNotUseButton').show();
4199            document.getElementById('oneClickCheckoutForgetButton').show();
4200
4201            _fireDefaultStateShownEvent();
4202             
4203        }
4204
4205    };
4206
4207    const _setOneClickUseAccountState = function () {
4208
4209        if (_initialized) {
4210
4211            _setOneClickHideAll();
4212
4213            _oneClickCheckOutSelectedByUser = true;            
4214
4215            document.getElementById('oneClickTopRow').show();
4216
4217            document.getElementById('oneClickCheckoutMessagesContainer').show();
4218            document.getElementById('oneClickCheckoutAllSetMessage').show();
4219
4220            _fireUseAccountSelectedEvent();
4221             
4222        }
4223
4224    };
4225
4226    const _setOneClickDoNotUseState = function () {
4227
4228        if (_initialized) {
4229
4230            _setOneClickHideAll();
4231
4232            _oneClickCheckOutSelectedByUser = false;
4233                        
4234            document.getElementById('oneClickTopRow').show();
4235
4236            document.getElementById('oneClickCheckoutMessagesContainer').show();
4237            document.getElementById('oneClickCheckoutUndoUseButton').show();
4238
4239            _fireDontUseEvent();
4240             
4241        }
4242
4243    };
4244
4245    const _setOneClickForgetMyInfoState = function () {
4246
4247        if (_initialized) {
4248
4249            _oneClickCheckOutSelectedByUser = false;
4250
4251            _oneClickOptContactOut();
4252
4253            _setOneClickHideAll();
4254
4255            _fireForgetInfoEvent();
4256             
4257        }
4258
4259    };
4260
4261    const _handleOneClickUseAccountClick = function () { _setOneClickUseAccountState(); };
4262    const _handleOneClickDoNotUseClick = function () { _setOneClickDoNotUseState(); };
4263    const _handleOneClickChangedMindClick = function () { _setOneClickDefaultState(); };
4264    const _handleForgetMyInfoClick = function () { _setOneClickForgetMyInfoState(); };
4265
4266    const _getOnclickOptOutKeyName = function () { let retVal = ''; if (thisApi.Contact_Id > 0) retVal = 'contact_' + thisApi.Contact_Id + '_oneClickedOptedOut'; return retVal; };
4267
4268    const _isContactOptedOutOfOneClick = function () { return App.LocalStorage.GetValueOrFalse(_getOnclickOptOutKeyName()); };
4269
4270    const _oneClickOptContactOut = function () { if (thisApi.Contact_Id > 0) App.LocalStorage.Save(_getOnclickOptOutKeyName(), true); };
4271    const _oneClickOptContactIn = function (contact_id) { thisApi.Contact_Id = contact_id; if (thisApi.Contact_Id > 0) App.LocalStorage.Save(_getOnclickOptOutKeyName(), false); };
4272
4273    const _getOneClickTempDisableKeyName = function () { let retVal = ''; if (thisApi.Contact_Id > 0) retVal = 'contact_' + thisApi.Contact_Id + '_oneClickedTemporarilyDisabled'; return retVal; };
4274
4275    const _disableTemporarily = function () {
4276
4277        if (_initialized && thisApi.Contact_Id > 0) {
4278
4279            _oneClickCheckOutSelectedByUser = false;
4280             
4281            App.LocalStorage.SaveForOneHour(_getOneClickTempDisableKeyName(), true);
4282
4283            _setOneClickHideAll();
4284
4285            _fireDisabledEvent();
4286
4287        }
4288
4289    };
4290
4291    thisApi.DisableTemporarily = function () { _disableTemporarily(); };
4292
4293    const _enable = function () {
4294
4295        if (_initialized && thisApi.Contact_Id > 0)                          
4296            App.LocalStorage.Save(_getOneClickTempDisableKeyName(), false);                     
4297
4298    };
4299
4300    thisApi.Enable = function () { _enable(); };
4301
4302    const _isDisabled = function () {
4303
4304        let retVal = false;
4305
4306        if (thisApi.Contact_Id > 0)             
4307            retVal = App.LocalStorage.GetValueOrFalse(_getOneClickTempDisableKeyName());        
4308
4309        return retVal;
4310
4311    };         
4312
4313    let _initialized = false;
4314
4315    thisApi.SetOneClickForgetMyInfoState = function () { _setOneClickForgetMyInfoState(); };
4316
4317    //Initialize
4318    thisApi.Initialize = function () {
4319
4320        _logger.Log('initialize started');        
4321
4322        if (!_initialized) {
4323             
4324            if (thisApi.ParentElement != null && thisApi.AddAfterElement != null) {  
4325
4326                _logger.Log('init one-click check out for contact ' + thisApi.Contact_Id + ' with profile ' + thisApi.PaymentProfile_Id + ' (' + thisApi.PaymentProfileDescription + ')');                        
4327
4328                if (thisApi.Contact_Id > 0 && thisApi.PaymentProfile_Id > 0 && !isBlank(thisApi.PaymentProfileDescription)) {
4329                     
4330                    //check for opt out
4331                    let optedOut = _isContactOptedOutOfOneClick();
4332                    if (!optedOut) {
4333
4334                        let tempDisabled = _isDisabled();
4335                        if (!tempDisabled) { 
4336
4337                            //build UI                          
4338                            _initialized = true;
4339
4340                            //allow reinitialization
4341                            if (document.getElementById('oneClickBottomRow') == null) {
4342
4343                                let oneClickBottomRow = createDomElement('div', 'oneClickBottomRow', thisApi.FormRowCssClass); 
4344                             
4345                                let onClickCont = createDomElement('div', 'oneClickCheckoutContainer', 'ce-oneClickCheckoutContainers');
4346
4347                                onClickCont.addClickInputButton('loading...', function () { _handleOneClickUseA
4347ccountClick(); }, 'oneClickCheckoutUseProfileButton');
4348                                onClickCont.addClickInputButton('Use Another Method of Payment', function () { _handleOneClickDoNotUseClick(); }, 'oneClickCheckoutDoNotUseButton');
4349
4350                                onClickCont.addHtmlClickLink('Don\'t show this again', function () { _handleForgetMyInfoClick(); }, 'oneClickCheckoutForgetButton');
4351
4352                                oneClickBottomRow.appendChild(onClickCont);
4353                                    
4354                                thisApi.ParentElement.insertAfterElement(oneClickBottomRow, thisApi.AddAfterElement);
4355                                    
4356                                let oneClickTopRow = createDomElement('div', 'oneClickTopRow', thisApi.FormRowCssClass); 
4357
4358                                let onClickMessCont = createDomElement('div', 'oneClickCheckoutMessagesContainer');
4359
4360                                onClickMessCont.addText('Use your payment info from last time?', 'oneClickCheckoutInitialQuestion');
4361
4362                                let allSetMessage = document.createElement('div');
4363                                allSetMessage.id = 'oneClickCheckoutAllSetMessage';
4364
4365                                allSetMessage.addText('You\'re all set! Click button below to pay. ');
4366                                allSetMessage.addAnonymousClickLink('Click here', function () { _handleOneClickChangedMindClick(); });
4367                                allSetMessage.addText(' to use another payment method.');
4368
4369                                onClickMessCont.appendChild(allSetMessage);
4370
4371                                onClickMessCont.addHtmlClickLink('Click here to use your payment info from last time.', function () { _handleOneClickChangedMindClick(); }, 'oneClickCheckoutUndoUseButton');
4372
4373                                oneClickTopRow.appendChild(onClickMessCont);
4374                                     
4375                                thisApi.ParentElement.insertAfterElement(oneClickTopRow, thisApi.AddAfterElement);
4376                                     
4377                            }
4378                            
4379                            document.getElementById('oneClickCheckoutUseProfileButton').setAttribute('value', 'Yes - Use ' + thisApi.PaymentProfileDescription);
4380                             
4381                            _setOneClickDefaultState();
4382
4383                        }
4384                        else
4385                            _logger.Log('one click disabled for 1 hour for visitor');
4386
4387                    }
4388                    else
4389                        _logger.Log('visitor opted out of one click');
4390
4391                }
4392                else
4393                    _logger.Log('visitor context invalid');
4394
4395
4396                _initialized = true;
4397
4398            }
4399            else
4400                _logger.Log('missing required parent element and insert after element for one click check out api');
4401
4402        }
4403        else
4404            _logger.Log('skipping one click check out api initialization (already initialized)');
4405
4406        _fireInitializedEvent();
4407
4408    };
4409
4410    if (thisApi.AutoInitialize) {
4411
4412        document.addEventListener('DOMContentLoaded', function () { thisApi.Initialize(); });
4413
4414    }
4415
4416    return thisApi;
4417
4418}
4419  
4420function getPageShoppingCartCallOutApi(config) {
4421
4422    const thisApi = {};
4423
4424    thisApi.Mode = 'Floating'; //no config right now (v1)
4425
4426    thisApi.ShoppingCartUrl = config.ShoppingCartUrl;
4427    thisApi.ShoppingCartItemCount = (config.ShoppingCartItemCount != null) ? config.ShoppingCartItemCount : 0;
4428     
4429    const _logger = getConsoleLogApi({"LogSourceName": 'CE Shopping Cart Call Out Api' });
4430    
4431    const _tryGetCtaLinkElem = function () { return document.querySelector('.ce-floatingCartCta'); };
4432
4433    thisApi.Show = function () {if (_tryGetCtaLinkElem() != null) _tryGetCtaLinkElem().show();};
4434    thisApi.Hide = function () {if (_tryGetCtaLinkElem() != null) _tryGetCtaLinkElem().vanish();};
4435     
4436    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
4437
4438        CallBackManager.add('pageShoppingCartCallOutApiInitializedCallBack', callBackFunction, null);
4439
4440    };
4441
4442    const _fireInitializedEvent = function () {
4443        
4444        CallBackManager.fire('pageShoppingCartCallOutApiInitializedCallBack', {}, null);
4445
4446    };
4447      
4448    //Initialize
4449    thisApi.Initialize = function () {
4450
4451        _logger.Log('initialize shopping cart call out started');
4452
4453        if (_tryGetCtaLinkElem() != null) {
4454            _logger.Log('no body in  dom (abort initialization)');
4455            return;
4456        }
4457
4458        if (isBlank(thisApi.ShoppingCartUrl)) {
4459            _logger.Log('shopping cart url required (abort initialization)');
4460            return;
4461        }
4462
4463        if (_tryGetCtaLinkElem() != null) {
4464            _logger.Log('shopping cart call out already present in dom (abort initialization)');
4465            return;
4466        }
4467         
4468        const mainLinkElem = document.createElement('a');
4469        mainLinkElem.href = thisApi.ShoppingCartUrl;
4470        mainLinkElem.addClassName('ce-floatingCartCta');
4471              
4472        const svgElem = createSvgElement();
4473        svgElem.addClassName('ce-floatingCartCtaIcon');
4474        svgElem.setAttribute('viewBox', '0 0 24 24');
4475
4476        const svgPathElem = createSvgPathElement();
4477        svgPathElem.setAttribute('d',
4478                                'M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2z ' +
4479                                'M17 18c-1.1 0-1.99.9-1.99 2S15.9 22 17 22s2-.9 2-2-.9-2-2-2z ' +
4480                                'M7.17 14.75l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03 ' +
4481                                'L21.16 5H6.21l-.94-2H2v2h2l3.6 7.59-1.35 2.44 ' +
4482                                'C6.09 15.32 6 15.65 6 16c0 1.1.9 2 2 2h12v-2H8.42 ' +
4483                                'c-.14 0-.25-.11-.25-.25z');
4484
4485        svgElem.appendChild(svgPathElem);
4486
4487        mainLinkElem.appendChild(svgElem);
4488         
4489        const countIconElem = document.createElement('span');
4490        countIconElem.addClassName('ce-floatingCartCtaCount');
4491
4492        countIconElem.innerText = formatNumber(thisApi.ShoppingCartItemCount);
4493
4494        mainLinkElem.appendChild(countIconElem);
4495
4496        document.body.appendChild(mainLinkElem);
4497
4498        if (thisApi.ShoppingCartItemCount <= 0)
4499            thisApi.Hide();
4500
4501        _fireInitializedEvent();
4502
4503    };
4504     
4505    return thisApi;
4506
4507}
4508
4509function getShoppingCartProductApi(config) {
4510
4511    const thisApi = {};
4512
4513    const _product = config.Product;
4514
4515    const _productId = _product.Product_Id || 0;
4516
4517    const _productContainerDomId = config.ProductContainerDomId;
4518
4519    const _variantDataArray = _product.VariantDataArray || [];
4520
4521    const _customFields = _product.CustomFields || [];
4522
4523    const _attributes = _product.Attributes || [];
4524
4525    const _basePrice = _product.BasePrice || 0;
4526
4527    const _baseInventory = _product.BaseInventory || 0;
4528
4529    const _galleryImages = _product.GalleryImages || [];
4530     
4531    const _siteInventoryConfig = config.SiteInventoryConfig || {};
4532
4533    const _logger = getConsoleLogApi({"LogSourceName": 'CE Shopping Cart Product Api'});
4534
4535    Object.defineProperty(thisApi, 'Product_Id', {
4536        get: function () {
4537            return _productId;
4538        }
4539    });
4540
4541    const _tryGetProductContainerElem = function () {return document.getElementById(_productContainerDomId);};
4542
4543    Object.defineProperty(thisApi, 'ContainerDomElement', {
4544        get: function () {
4545            return _tryGetProductContainerElem();
4546        }
4547    });
4548
4549    const _getDisabledDropDownElements = function () {return _tryGetProductContainerElem().querySelectorAll('[data-noOptionsAvailable="true"]'); };
4550
4551    Object.defineProperty(thisApi, 'DisabledDropDownDomElements', {
4552        get: function () {
4553            return _getDisabledDropDownElements();
4554        }
4555    });
4556
4557    Object.defineProperty(thisApi, 'HasAvailableProducts', {
4558        get: function () {
4559            return !_tryGetProductContainerElem().hasClassName('.ce-productHasNoAvailableOptions');
4560        }
4561    });
4562
4563    const _getQuantityElem = function () {return _tryGetProductContainerElem().querySelector('.ce-addToCartQuantities');};
4564
4565    Object.defineProperty(thisApi, 'QuantityDomElement', {
4566        get: function () {
4567            return _getQuantityElem();
4568        }
4569    });
4570
4571    const _getCustomerSetPriceElem = function () {return _tryGetProductContainerElem().querySelector('.ce-addToCartCustomerSetPriceInputs');};
4572
4573    Object.defineProperty(thisApi, 'SetPriceDomElement', {
4574        get: function () {
4575            return _getCustomerSetPriceElem();
4576        }
4577    });
4578
4579    const _getCustomFieldElems = function (customField_id) {return _tryGetProductContainerElem().querySelector('.ce-shoppingCartCustomFields');};
4580
4581    Object.defineProperty(thisApi, 'CustomFieldDomElements', {
4582        get: function () {
4583            return _getCustomFieldElems();
4584        }
4585    });
4586
4587    const _getCustomFieldElem = function (customField_id) {return _tryGetProductContainerElem().querySelector('.ce-shoppingCartCustomFields[data-custom-field-id="' + customField_id + '"]');};
4588
4589    const _getCustomFieldOptionElems = function (customField_id) {return _tryGetProductContainerElem().querySelectorAll('.ce-customFieldOption[data-custom-field-id="' + customField_id + '"]');};
4590
4591    const _getAttrDropDownElem = function (attribute_id) {return _tryGetProductContainerElem().querySelector('.ce-attrDropDown[data-attribute-id="' + attribute_id + '"]');};
4592
4593    const _getAttrDropDownElems = function () {return _tryGetProductContainerElem().querySelectorAll('.ce-attrDropDown');};
4594
4595    Object.defineProperty(thisApi, 'AttributeDropDownDomElements', {
4596        get: function () {
4597            return _getAttrDropDownElems();
4598        }
4599    });
4600
4601    const _getGroupIdHiddenInputElem = function () {return _tryGetProductContainerElem().querySelector('[name="avGroupID"]');};
4602
4603    const _getPriceDisplayElem = function () {return document.getElementById('priceDisp' + _productId);};
4604
4605    const _getComparePriceDisplayElem = function () {return document.getElementById('comparePrice' + _productId);};
4606
4607    const _getInventoryMessageElem = function () {return document.getElementById('inventoryDisp' + _productId);};
4608
4609    const _getProductImageElem = function () {return document.getElementById('prodDetailImg-' + _productId);};
4610
4611    const _getProductImageLinkElem = function () {return document.getElementById('prodDetailImghyperlink-' + _productId);};
4612
4613    const _getGalleryThumbnailElems = function () {return _tryGetProductContainerElem().querySelectorAll('.ce-galleryThumbnail');};
4614
4615    const _getGalleryViewImageElem = function () {return _tryGetProductContainerElem().querySelector('#prod_viewImage');};
4616
4617    const _getGalleryViewImageLinkElem = function () {return _tryGetProductContainerElem().querySelector('#prod_viewImageLink');};
4618
4619    const _getAddToCartButtonCellElem = function () {return _tryGetProductContainerElem().querySelector('.ce-formSectionFooterButtonRows');};
4620
4621    const _getAddToCartButtonElem = function () {return _tryGetProductContainerElem().querySelector('.ce-addToCartButton');};
4622
4623    let _currentGroupId = null;
4624
4625    const _setCurrentGroupId = function (groupId) {
4626        _currentGroupId = groupId;
4627        const groupIdElem = _getGroupIdHiddenInputElem();
4628        if (groupIdElem != null) groupIdElem.value = groupId;
4629    };
4630
4631    const _getVariantCount = function () {return _variantDataArray.length;};
4632
4633    Object.defineProperty(thisApi, 'VariantCount', {
4634        get: function () {
4635            return _getVariantCount();
4636        }
4637    });
4638
4639    Object.defineProperty(thisApi, 'Variant_Id', {
4640        get: function () {
4641            return (_currentGroupId != null) ? _currentGroupId : 0;
4642        }
4643    });
4644
4645    const _tryGetQuantityValue = function () {
4646
4647        let retVal = 0;
4648
4649        const qtyElem = _getQuantityElem();
4650
4651        if (qtyElem != null)
4652            retVal = qtyElem.getValue().toIntZeroIfInvalid();
4653
4654        return retVal;
4655
4656    };
4657
4658    Object.defineProperty(thisApi, 'Quantity', {
4659        get: function () {return _tryGetQuantityValue();}
4660    });
4661
4662    const _tryGetCustomerSetPriceAmount = function () {
4663
4664        let retVal = 0.00;
4665
4666        const setPriceElem = _getCustomerSetPriceElem();
4667        if (setPriceElem != null)
4668            retVal = setPriceElem.getValue().toDecimalZeroIfInvalid();
4669
4670        return retVal;
4671
4672    };
4673
4674    Object.defineProperty(thisApi, 'CustomerSetPrice', {
4675        get: function () {return _tryGetCustomerSetPriceAmount();}
4676    });
4677
4678    const _getCustomFieldValue = function (field) {
4679
4680        if (field.FieldTypeId == 1 || field.FieldTypeId == 4) {
4681            const fieldElem = _getCustomFieldElem(field.FieldId);
4682            return (fieldElem != null) ? fieldElem.value : '';
4683        }
4684
4685        const optionElems = _getCustomFieldOptionElems(field.FieldId);
4686        const checkedValues = [];
4687        const len = optionElems.length;
4688
4689        for (let x = 0;x < len;x++)
4690            if (optionElems[x].checked)
4691                checkedValues.push(optionElems[x].value);
4692
4693        return checkedValues.join(',');
4694
4695    };
4696
4697    Object.defineProperty(thisApi, 'CustomFields', {
4698        get: function () {
4699
4700            const result = [];
4701
4702            _customFields.forEach(function (field) {
4703                result.push({Id: field.FieldId, Value: _getCustomFieldValue(field)});
4704            });
4705
4706            return result;
4707
4708        }
4709    });
4710
4711    const _setDropDownsByGroup = function (arrObj, refGroupId) {
4712
4713        if (arrObj.length > 0) {
4714
4715            for (let x = 0;x < arrObj.length;++x) {
4716
4717                const attrValArr = arrObj[x][0].toString().split('|');
4718                const attrArr = arrObj[x][4].toString().split(',');
4719
4720                if (arrObj[x][3] == refGroupId) {
4721
4722                    for (let y = 0;y < attrArr.length;++y) {
4723
4724                        const ddObj = _getAttrDropDownElem(attrArr[y]);
4725
4726                        if (ddObj != null) {
4727
4728                            for (let z = 0;z < ddObj.length;++z) {
4729
4730                                for (let i = 0;i < attrValArr.length;++i) {
4731
4732                                    if (ddObj.options[z].value == attrValArr[i])
4733                                        ddObj.selectedIndex = z;
4734
4735                                }
4736
4737                            }
4738
4739                        }
4740
4741                    }
4742
4743                }
4744
4745            }
4746
4747        }
4748
4749    };
4750
4751    const _setDropDownByAttrValId = function (attributeId, attrValId) {
4752
4753        const ddObj = _getAttrDropDownElem(attributeId);
4754
4755        for (let x = 0;x < ddObj.length;++x) {
4756
4757            if (ddObj.options[x].value == attrValId)
4758                ddObj.selectedIndex = x;
4759
4760        }
4761
4762    };
4763
4764    const _getSelectedAttrValue = function (attributeId) {
4765
4766        const ddObj = _getAttrDropDownElem(attributeId);
4767
4768        _logger.Log('_getSelectedAttrValue for id ' + attributeId + ' (elem: ' + ddObj + ')');
4769
4770        if (ddObj != null)
4771            _logger.Log('ddObj.selectedIndex: ' + ddObj.selectedIndex);
4772
4773        if (ddObj == null || ddObj.selectedIndex < 0)
4774            return null;
4775
4776        if (ddObj.getAttribute('data-noOptionsAvailable') == 'true')
4777            return null;
4778
4779        return ddObj.options[ddObj.selectedIndex].value;
4780
4781    };
4782
4783    const _updateProductImageForSelection = function () {
4784
4785        _attributes.forEach(function (attribute) {
4786
4787            const selectedValueId = _getSelectedAttrValue(attribute.Id);
4788
4789            if (selectedValueId == null)
4790                return;
4791
4792            let matchedValue = null;
4793
4794            for (let x = 0;x < attribute.Values.length;x++) {
4795
4796                if (attribute.Values[x].Id == selectedValueId) {
4797                    matchedValue = attribute.Values[x];
4798                    break;
4799                }
4800
4801            }
4802
4803            if (matchedValue != null && matchedValue.Image) {
4804
4805                const imgElem = _getProductImageElem();
4806
4807                if (imgElem != null) {
4808
4809                    let newSrc = imgElem.src.replace(/\/[^\/]*$/, '/' + matchedValue.Image);
4810                    newSrc = newSrc.replace('/_thumbs/', '/_images/').replace('/_hires/', '/_images/');
4811                    imgElem.src = newSrc;
4812
4813                    const linkElem = _getProductImageLinkElem();
4814                    if (linkElem != null) linkElem.href = newSrc;
4815
4816                }
4817
4818            }
4819
4820        });
4821
4822    };
4823
4824    const _findMatchingVariantIndex = function () {
4825
4826        let matchIndex = 0;
4827
4828        if (_attributes.length === 0)
4829            return matchIndex;
4830
4831        for (let x = 0;x < _variantDataArray.length;++x) {
4832
4833            const innerArr = _variantDataArray[x][0].split('|');
4834            let matches = 0;
4835
4836            for (let y = 0;y < innerArr.length;++y) {
4837
4838                for (let z = 0;z < _attributes.length;z++) {
4839
4840                    if (innerArr[y] == _getSelectedAttrValue(_attributes[z].Id))
4841                        matches = matches + 1;
4842
4843                }
4844
4845            }
4846
4847            if (matches == innerArr.length)
4848                matchIndex = x;
4849
4850        }
4851
4852        return matchIndex;
4853
4854    };
4855
4856    const _getPrice = function () {
4857
4858        let retVal = _basePrice;
4859
4860        const matchIndex = _findMatchingVariantIndex();
4861
4862        if (_variantDataArray.length > 0)
4863            retVal = (_basePrice + _variantDataArray[matchIndex][1]);
4864
4865        return retVal;
4866
4867    };
4868
4869    //Object.defineProperty(thisApi, 'Price', {
4870    //    get: function () {            
4871    //        return _getPrice();
4872    //    }
4873    //});
4874
4875    const _updatePrice = function (obj) {
4876
4877        const matchIndex = _findMatchingVariantIndex();
4878
4879        //console.log('update price FIRED!!!!!!');
4880
4881        _updateProductImageForSelection();
4882
4883        //_logger.Log('UpdatePrice matchIndex: ' + matchIndex);
4884
4885        if (_variantDataArray.length > 0)
4886            _setCurrentGroupId(_variantDataArray[matchIndex][3]);
4887        else
4888            _setCurrentGroupId(0);
4889
4890        if (_variantDataArray.length > 0) {
4891
4892            const inventory = _variantDataArray[matchIndex][2];
4893            const basePrice = _basePrice + _variantDataArray[matchIndex][1];
4894
4895            const priceElem = _getPriceDisplayElem();
4896            if (priceElem != null) priceElem.innerHTML = '$' + basePrice.toFixed(2);
4897
4898            const compareElem = _getComparePriceDisplayElem();
4899            if (compareElem != null) {
4900
4901                if (_variantDataArray[matchIndex][5] > 0) {
4902                    const compPrice = _variantDataArray[matchIndex][5];
4903                    compareElem.innerHTML = '$' + compPrice.toFixed(2);
4904                }
4905                else
4906                    compareElem.innerHTML = '';
4907
4908            }
4909
4910            let thisInvValueTemplate = _variantDataArray[matchIndex][6];
4911            let thisInvValueTemplateSO = _variantDataArray[matchIndex][7];
4912
4913            if (isBlank(thisInvValueTemplate))
4914                thisInvValueTemplate = _siteInventoryConfig.DispTemplate || '^*^INVVAL^*^';
4915
4916            if (isBlank(thisInvValueTemplateSO))
4917                thisInvValueTemplateSO = _siteInventoryConfig.DispTemplateSO || '^*^INVVAL^*^';
4918
4919            _logger.Log('UpdatePrice show inv template: ' + thisInvValueTemplate);
4920            _logger.Log('UpdatePrice sold out template: ' + thisInvValueTemplateSO);
4921
4922            const invElem = _getInventoryMessageElem();
4923
4924            if (invElem != null) {
4925
4926                if (_siteInventoryConfig.TrackingEnabled && _product.UseInventory && _siteInventoryConfig.GlShowInventory && _product.ShowInventory)
4927                    invElem.innerHTML = inventory > 0 ? thisInvValueTemplate.replace('^*^INVVAL^*^', inventory.toString()) : thisInvValueTemplateSO.replace('^*^INVVAL^*^', inventory.toString());
4928                else if (_siteInventoryConfig.TrackingEnabled && _product.UseInventory)
4929                    invElem.innerHTML = inventory > 0 ? '' : 'SOLD OUT';
4930
4931            }
4932
4933            if (_product.UseInventory) {
4934
4935                const qtyElem = _getQuantityElem();
4936                const addToCartCellElem = _getAddToCartButtonCellElem();
4937
4938                if (qtyElem != null) {
4939
4940                    const curQty = qtyElem.getValue().toIntZeroIfInvalid();
4941
4942                    const origConfiguredMaxAllowed = qtyElem.tryGetIntegerAttribute('data-configuredOriginalMaximumNumericValue');
4943
4944                    let setMaxAllowedVal = inventory;
4945
4946                    if (origConfiguredMaxAllowed > 0 && inventory > origConfiguredMaxAllowed)
4947                        setMaxAllowedVal = origConfiguredMaxAllowed;
4948
4949                    qtyElem.setAttribute('data-validationMaximumNumericValue', setMaxAllowedVal);
4950
4951                    if (inventory > 0)
4952                        qtyElem.enable();
4953                    else
4954                        qtyElem.disable();
4955
4956                }
4957
4958            }
4959
4960            const variantStatus = _variantDataArray[matchIndex][11];
4961
4962            _logger.Log('UpdatePrice variantStatus: ' + variantStatus);
4963
4964            if (variantStatus == 0) {
4965
4966                let inactiveDisplayText = _variantDataArray[matchIndex][12];
4967
4968                if (isBlank(inactiveDisplayText))
4969                    inactiveDisplayText = 'Unavailable';
4970
4971                _logger.Log('UpdatePrice called by: ' + (obj != null ? obj.name : '(none)'));
4972
4973                if (priceElem != null && obj != null && obj.name != null && obj.name.length > 0)
4974                    priceElem.innerHTML = (inactiveDisplayText != null) ? inactiveDisplayText : '';
4975
4976                if (compareElem != null) compareElem.innerHTML = '';
4977                if (invElem != null) invElem.innerHTML = '';
4978
4979            }
4980
4981        }
4982
4983    };
4984
4985    const _setAttributeValue = function (attributeId, attrValId) {
4986
4987        const ddObj = _getAttrDropDownElem(attributeId);
4988
4989        if (ddObj == null) return;
4990
4991        _setDropDownByAttrValId(attributeId, attrValId);
4992
4993        ddObj.dispatchEvent(new Event('change', {bubbles: true}));
4994
4995    };
4996
4997    const _updateAttrDropdownsForSku = function (sku) {
4998
4999        for (let x = 0;x < _variantDataArray.length;++x) {
5000
5001            if (_variantDataArray[x][3] == sku) {
5002
5003                const attrIds = _variantDataArray[x][4].toString().split(',');
5004                const attrValIds = _variantDataArray[x][0].toString().split('|');
5005
5006                for (let y = 0;y < attrIds.length;y++) {
5007
5008                    const ddObj = _getAttrDropDownElem(attrIds[y]);
5009
5010                    if (ddObj != null && attrValIds[y] != null) {
5011                        ddObj.value = attrValIds[y];
5012                        ddObj.dispatchEvent(new Event('change', {bubbles: true}));
5013                    }
5014
5015                }
5016
5017            }
5018
5019        }
5020
5021    };
5022
5023    const _swapGalleryImage = function (thumbnailElem) {
5024
5025        if (thumbnailElem == null) return;
5026
5027        const passId = thumbnailElem.id;
5028
5029        for (let x = 0;x < _galleryImages.length;x++) {
5030
5031            const galleryImage = _galleryImages[x];
5032            const thisImgObj = document.getElementById(galleryImage.ElemId);
5033
5034            if (thisImgObj == null) continue;
5035
5036            if (thisImgObj.id == passId) {
5037
5038                const viewImgLink = _getGalleryViewImageLinkElem();
5039                const viewImgObj = _getGalleryViewImageElem();
5040
5041                if (galleryImage.Inventory > 0 || galleryImage.SoldOutMediumImageUrl.length == 0 || galleryImage.SoldOutLargeImageUrl.length == 0) {
5042                    if (viewImgLink != null) viewImgLink.href = galleryImage.LargeImageUrl;
5043                    if (viewImgObj != null) viewImgObj.src = galleryImage.MediumImageUrl;
5044                }
5045                else {
5046                    if (viewImgLink != null) viewImgLink.href = galleryImage.SoldOutLargeImageUrl;
5047                    if (viewImgObj != null) viewImgObj.src = galleryImage.SoldOutMediumImageUrl;
5048                }
5049
5050                thisImgObj.classList.add('ce-galleryThumbnailActive');
5051
5052            }
5053            else {
5054                thisImgObj.classList.remove('ce-galleryThumbnailActive');
5055            }
5056
5057        }
5058
5059        const attributeId = thumbnailElem.getAttribute('data-attribute-id');
5060        const attrValId = thumbnailElem.getAttribute('data-attr-val-id');
5061        const groupId = thumbnailElem.getAttribute('data-group-id');
5062
5063        if (attributeId && attrValId)
5064            _setAttributeValue(parseInt(attributeId), attrValId);
5065        else if (groupId)
5066            _updateAttrDropdownsForSku(parseInt(groupId));
5067
5068    };
5069
5070    const _updateAttributeOptionAvailability = function (useInventory) {
5071
5072        let diabledDropDownCnt = 0;
5073
5074        _attributes.forEach(function (attribute) {
5075
5076            const ddObj = _getAttrDropDownElem(attribute.Id);
5077
5078            if (ddObj == null) return;
5079
5080            for (let x = 0;x < ddObj.options.length;x++) {
5081
5082                if (ddObj.options[x].getAttribute('data-noOptionsPlaceholder') == 'true') continue;
5083
5084                const optionValue = ddObj.options[x].value;
5085                let attributeOutOfStock = true;
5086
5087                for (let y = 0;y < _variantDataArray.length;++y) {
5088
5089                    const innerArr = _variantDataArray[y][0].toString().split('|');
5090                    const variantExistsInDropdown = innerArr.indexOf(optionValue) !== -1;
5091
5092                    if (variantExistsInDropdown) {
5093
5094                        const rowInventory = _variantDataArray[y][2];
5095                        const rowStatus = _variantDataArray[y][11];
5096
5097                        if (!((useInventory && rowInventory <= 0) || rowStatus == 0))
5098                            attributeOutOfStock = false;
5099
5100                    }
5101
5102                }
5103
5104                if (attributeOutOfStock) {
5105                    ddObj.options[x].style.display = 'none';
5106                    ddObj.options[x].setAttribute('disabled', 'disabled');
5107                }
5108                else {
5109                    ddObj.options[x].style.display = '';
5110                    ddObj.options[x].removeAttribute('disabled');
5111                }
5112
5113            }
5114
5115            let foundAvailable = false;
5116
5117            for (let x = 0;x < ddObj.options.length;x++) {
5118
5119                if (ddObj.options[x].getAttribute('data-noOptionsPlaceholder') == 'true') continue;
5120
5121                if (ddObj.options[x].style.display != 'none') {
5122                    ddObj.selectedIndex = x;
5123                    foundAvailable = true;
5124                    break;
5125                }
5126
5127            }
5128
5129            let placeholderOption = null;
5130
5131            for (let x = 0;x < ddObj.options.length;x++) {
5132                if (ddObj.options[x].getAttribute('data-noOptionsPlaceholder') 
5132== 'true') {
5133                    placeholderOption = ddObj.options[x];
5134                    break;
5135                }
5136            }
5137
5138            if (!foundAvailable) {
5139
5140                if (placeholderOption == null) {
5141                    placeholderOption = document.createElement('option');
5142                    placeholderOption.setAttribute('data-noOptionsPlaceholder', 'true');
5143                    placeholderOption.value = '';
5144                    placeholderOption.text = 'No options available';
5145                    ddObj.insertBefore(placeholderOption, ddObj.options[0] || null);
5146                }
5147
5148                placeholderOption.style.display = '';
5149                placeholderOption.removeAttribute('disabled');
5150                ddObj.selectedIndex = placeholderOption.index;
5151                ddObj.setAttribute('disabled', 'disabled');
5152                ddObj.setAttribute('data-noOptionsAvailable', 'true');
5153
5154                diabledDropDownCnt++;
5155
5156            }
5157            else {
5158
5159                ddObj.removeAttribute('disabled');
5160                ddObj.removeAttribute('data-noOptionsAvailable');
5161
5162                if (placeholderOption != null) {
5163                    placeholderOption.style.display = 'none';
5164                    placeholderOption.setAttribute('disabled', 'disabled');
5165                }
5166
5167            }
5168
5169        });
5170
5171        if (diabledDropDownCnt > 0)
5172            _tryGetProductContainerElem().addClassName('ce-productHasNoAvailableOptions');
5173        else
5174            _tryGetProductContainerElem().removeClassName('ce-productHasNoAvailableOptions');
5175
5176    };
5177
5178    const _validateProductConfiguration = function () {
5179         
5180        let carryOn = true;
5181
5182        for (let x = 0;x < _attributes.length;x++) {
5183
5184            const ddObj = _getAttrDropDownElem(_attributes[x].Id);
5185
5186            if (ddObj != null) {
5187
5188                ddObj.validationSucceeded();
5189
5190                if (ddObj.getAttribute('data-noOptionsAvailable') == 'true') {
5191                    carryOn = false;
5192                    ddObj.validationFailed();
5193                }
5194
5195            }
5196
5197        }
5198        
5199        //let prodErrorDesc = _product.Name;
5200        //if(isBlank(prodErrorDesc))
5201        //    prodErrorDesc = 'Selection';
5202
5203        //check status of variant
5204        if (carryOn && _variantDataArray.length > 0 && _currentGroupId != null) {
5205
5206            for (let x = 0;x < _variantDataArray.length;++x) {
5207
5208                if (_variantDataArray[x][3] == _currentGroupId && parseInt(_variantDataArray[x][11]) == 0) {                   
5209                    carryOn = false;
5210                    App.UI.Messages.SetError('Selection is currently not available');                                            
5211                }
5212
5213            }
5214
5215        }
5216
5217        //check inventory on variant or base
5218        if (carryOn && _siteInventoryConfig.TrackingEnabled && _product.UseInventory) {
5219
5220            const qtyElem = _getQuantityElem();
5221            const quantVal = (qtyElem != null) ? parseInt(qtyElem.value) : 0;
5222
5223            if (_variantDataArray.length > 0 && _currentGroupId != null) 
5224            {
5225
5226                for (let x = 0;x < _variantDataArray.length;++x) {
5227
5228                    if (_variantDataArray[x][3] == _currentGroupId) {
5229
5230                        const rowInventory = parseInt(_variantDataArray[x][2]);
5231
5232                        if (rowInventory <= 0) {
5233                            carryOn = false;
5234                            App.UI.Messages.SetError('Selection is currently sold out');   
5235                        }
5236                        else if (quantVal > rowInventory) {
5237                            carryOn = false;                            
5238                            App.UI.Messages.SetError('Only ' + rowInventory + ' remaining');
5239                        }
5240
5241                    }
5242
5243                }
5244
5245            }
5246            else 
5247            {
5248
5249                if (_baseInventory <= 0) {
5250                    carryOn = false;
5251                    App.UI.Messages.SetError('Selection is currently sold out');                       
5252                }
5253                else if (quantVal > _baseInventory) {
5254                    carryOn = false;
5255                    App.UI.Messages.SetError('Only ' + _baseInventory + ' remaining');                    
5256                }
5257
5258            }
5259
5260        }
5261
5262        if (!carryOn)
5263            return carryOn;
5264
5265        _customFields.forEach(function (field) {
5266
5267            if (!carryOn) return;
5268
5269            if (field.FieldTypeId == 2 || field.FieldTypeId == 3) {
5270
5271                if (field.Required) {
5272
5273                    const optionElems = _getCustomFieldOptionElems(field.FieldId);
5274                    let anyChecked = false;
5275
5276                    if (optionElems != null) {
5277
5278                        for (let x = 0;x < optionElems.length;x++)
5279                            optionElems[x].validationSucceeded();
5280
5281                        for (let x = 0;x < optionElems.length;x++) {
5282                            if (optionElems[x].checked) {
5283                                anyChecked = true;
5284                                break;
5285                            }
5286                        }
5287
5288                        if (!anyChecked)
5289                            for (let x = 0;x < optionElems.length;x++)
5290                                optionElems[x].validationFailed();
5291
5292                    }
5293
5294                    if (!anyChecked) {
5295                        
5296                        carryOn = false;
5297
5298                        if (!isBlank(field.ClientSideErrorMessage))
5299                            App.UI.Messages.SetError(field.ClientSideErrorMessage);   
5300                        
5301                    }
5302
5303                }
5304
5305            }
5306
5307        });
5308
5309        return carryOn;
5310
5311    };
5312
5313    const _generateAjaxPayloadParameters = function () {
5314
5315        const unitPrice = _getPrice();
5316
5317        var params = {
5318            "Product_Id": _productId,
5319            "Variant_Id": thisApi.Variant_Id,
5320            "Quantity": thisApi.Quantity,
5321            "CustomerSetPrice": thisApi.CustomerSetPrice,
5322            "CustomFields": thisApi.CustomFields,
5323            "UnitPrice": unitPrice,
5324            "TotalPrice": (thisApi.Quantity * unitPrice)
5325        };
5326
5327        return params;
5328
5329    };
5330
5331    Object.defineProperty(thisApi, 'SelectionDetails', {
5332        get: function () {
5333            return _generateAjaxPayloadParameters();
5334        }
5335    });
5336
5337    const _validateProductForm = function () {
5338        
5339        let validated = App.UI.Validation.ValidateForm(_tryGetProductContainerElem()); //NOTE: ValidateForm calls clear messages
5340
5341        if (validated)
5342            validated = _validateProductConfiguration();
5343
5344        return validated;
5345
5346    };
5347
5348    thisApi.Validate = function () {return _validateProductForm();};
5349
5350    const _submitAddToCart = function () {
5351
5352        const validated = _validateProductForm();
5353
5354        if (!validated)
5355            return;
5356
5357        App.UI.Messages.ClearMessages();
5358
5359        var params = _generateAjaxPayloadParameters();
5360
5361        App.PostJsonAsync('/api/CmsAppAjaxEndpoint/AddProductToCart',
5362            JSON.stringify(params),
5363            function (result) {
5364
5365                if (result != null && result.Success)
5366                    App.Navigation.GoToUrl(gl_shoppingCartUrl); //document.location = gl_shoppingCartUrl;                    
5367                else
5368                    App.UI.Messages.SetError(result.ErrorMessage);
5369
5370            });
5371
5372    };
5373
5374    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
5375        CallBackManager.add('shoppingCartProduct' + _productId + 'ApiInitializedCallBack', callBackFunction, null);
5376    };
5377
5378    const _fireInitializedEvent = function () {
5379
5380        const cbParams = {};
5381        cbParams.Product_Id = _productId;
5382
5383        CallBackManager.fire('shoppingCartProduct' + _productId + 'ApiInitializedCallBack', cbParams, null);
5384
5385    };
5386
5387    thisApi.Initialize = function () {
5388
5389        _logger.Log('initialize product ' + _productId + ' started');
5390
5391        if (_productId <= 0) {
5392            _logger.Log('product id required (abort initialization)');
5393            return;
5394        }
5395
5396        if (_tryGetProductContainerElem() == null) {
5397            _logger.Log('invalid form element (abort initialization)');
5398            return;
5399        }
5400
5401        if (_product == null) {
5402            _logger.Log('product config required (abort initialization)');
5403            return;
5404        }
5405
5406        //_logger.Log('initiailze attribute change events for ' + _attributes.length + ' attributes');
5407
5408        _attributes.forEach(function (attribute) {
5409
5410            const ddElem = _getAttrDropDownElem(attribute.Id);
5411
5412            _logger.Log('initiailze attribute change event for ' + ddElem.id);
5413            if (ddElem != null)
5414                ddElem.addEventListener('change', function () {_updatePrice(ddElem);});
5415
5416        });
5417
5418        const qtyElem = _getQuantityElem();
5419        if (qtyElem != null)
5420            qtyElem.addEventListener('change', function () {_updatePrice(qtyElem);});
5421
5422        const addToCartButtonElem = _getAddToCartButtonElem();
5423        if (addToCartButtonElem != null)
5424            addToCartButtonElem.addEventListener('click', function () {_submitAddToCart();});
5425
5426        const galleryThumbnailElems = _getGalleryThumbnailElems();
5427        if (galleryThumbnailElems != null)
5428            galleryThumbnailElems.forEach(function (thumbElem) {
5429                thumbElem.addEventListener('click', function (evt) {
5430                    if (evt != null && evt.preventDefault) evt.preventDefault();
5431                    _swapGalleryImage(thumbElem);
5432                });
5433            });
5434
5435        _updateAttributeOptionAvailability(_product.UseInventory);
5436
5437        _updatePrice(null);
5438
5439        _fireInitializedEvent();
5440
5441    };
5442
5443    return thisApi;
5444
5445}
5446
5447/*App Core Payment Plug-In Apis*/
5448
5449function getPayPalApi(config) {
5450
5451    const thisApi = {};
5452
5453    thisApi.PayPalEnabled = config.PayPalEnabled;
5454
5455    thisApi.PayPalLegacyButtonContainerDomId = config.PayPalLegacyButtonContainerDomId;
5456    thisApi.PayPalNewButtonContainerDomId = config.PayPalNewButtonContainerDomId;
5457    thisApi.PayPalPaymentTypeLabelDomId = config.PayPalPaymentTypeLabelDomId;
5458
5459    thisApi.PayPalMerchantGatewayId = config.PayPalMerchantGatewayId;
5460    thisApi.PayPalAccountMode = config.PayPalAccountMode;
5461
5462    thisApi.CharityEngineAppRecordId = config.CharityEngineAppRecordId;
5463
5464    thisApi.PaypalVenmoCheckOutEnabled = config.PaypalVenmoCheckOutEnabled;
5465
5466    thisApi.GetChargeAmountFunction = config.GetChargeAmountFunction;
5467    thisApi.GetRecurringFrequencyIdFunction = config.GetRecurringFrequencyIdFunction;
5468    thisApi.GetCurrencyIdFunction = config.GetCurrencyIdFunction;
5469
5470    thisApi.UIValidationCallBackFunction = config.UIValidationCallBackFunction;
5471
5472    const _logger = getConsoleLogApi({ "LogSourceName": 'CE PayPal Api' });
5473
5474    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
5475
5476        CallBackManager.add('payPalApiInitializedCallBack', callBackFunction, null);
5477
5478    };
5479
5480    thisApi.FireInitializedEvent = function () {
5481
5482        CallBackManager.fire('payPalApiInitializedCallBack', {}, null);
5483
5484    };
5485
5486    thisApi.RegisterTokenReceivedCallBack = function (callBackFunction) {
5487
5488        CallBackManager.add('payPalApiTokenReceivedCallBack', callBackFunction, null);
5489
5490    };
5491
5492    thisApi.FireTokenReceivedEvent = function (callBackParams) {
5493
5494        CallBackManager.fire('payPalApiTokenReceivedCallBack', callBackParams, null);
5495
5496    };
5497
5498    thisApi.RegisterPaymentAuthorizedCallBack = function (callBackFunction) {
5499
5500        CallBackManager.add('payPalApiPaymentAuthorizedCallBack', callBackFunction, null);
5501
5502    };
5503
5504    thisApi.FirePaymentAuthorizedEvent = function (callBackParams) {
5505
5506        CallBackManager.fire('payPalApiPaymentAuthorizedCallBack', callBackParams, null);
5507
5508    };
5509
5510    thisApi.IsPayPalApiAvailable = function () {
5511        return (window.paypal);
5512    }
5513
5514    thisApi.GetButtonsContainerElement = function () {
5515
5516        let btnElem = document.querySelector('div.paypal-button-container');
5517
5518        return btnElem;
5519
5520    };
5521
5522    thisApi.GetButtonElement = function () { return thisApi.GetButtonsContainerElement; }; //backwards compatibility (to be removed)
5523
5524    thisApi.GetLegacyButtonElement = function () {
5525
5526        let btnElem = document.getElementById(thisApi.PayPalLegacyButtonContainerDomId);
5527
5528        return btnElem;
5529
5530    };
5531
5532    thisApi.GetNewButtonElement = function () {
5533
5534        let btnElem = document.getElementById(thisApi.PayPalNewButtonContainerDomId);
5535
5536        return btnElem;
5537
5538    };
5539
5540    thisApi.GetPaymentTypeLabelElement = function () {
5541
5542        let btnElem = document.getElementById(thisApi.PayPalPaymentTypeLabelDomId);
5543
5544        return btnElem;
5545
5546    };
5547
5548    thisApi.RefreshButton = function () { thisApi.RefreshButtonIfShown(); };
5549
5550    thisApi.RefreshButtonIfShown = function () {
5551
5552        if (thisApi.GetButtonsContainerElement() != null) {
5553
5554            if (thisApi.GetButtonsContainerElement().isShown())
5555                thisApi.ShowButton();
5556
5557        }
5558
5559    };
5560
5561    thisApi.ShowButton = function () {
5562
5563        if (thisApi.GetButtonsContainerElement() != null) { 
5564
5565            //always keep hidden if paypal is not enabled
5566            thisApi.GetButtonsContainerElement().vanish();
5567            if(thisApi.PayPalEnabled)
5568                thisApi.GetButtonsContainerElement().show();
5569
5570        }
5571
5572        //temporary solution to show/hide the old versus new API
5573        if (thisApi.GetLegacyButtonElement() != null)
5574            thisApi.GetLegacyButtonElement().vanish();
5575
5576        if (thisApi.GetNewButtonElement() != null)
5577            thisApi.GetNewButtonElement().vanish();
5578
5579        _logger.Log('ShowButton Frequency: ' + thisApi.GetRecurringFrequencyIdFunction());
5580
5581        //show the old API if its a recurring payment or if we have the new check out disabled
5582        if (thisApi.GetRecurringFrequencyIdFunction() > 0 || !thisApi.PaypalVenmoCheckOutEnabled) {
5583
5584            _logger.Log('show recurring buttons');
5585
5586            if (thisApi.GetLegacyButtonElement() != null)
5587                thisApi.GetLegacyButtonElement().show();
5588
5589        }
5590        else {
5591
5592            _logger.Log('show one-time payment buttons');
5593
5594            if (thisApi.GetNewButtonElement() != null)
5595                thisApi.GetNewButtonElement().show();
5596
5597        }
5598
5599    };
5600
5601    thisApi.HideButton = function () {
5602
5603        if (thisApi.GetButtonsContainerElement() != null)
5604            thisApi.GetButtonsContainerElement().vanish();
5605
5606    };
5607
5608    //Initialize
5609    thisApi.Initialize = function () {
5610
5611        _logger.Log('initialize started');
5612
5613        _logger.Log('initialize legacy express check out front-end API');
5614
5615        let ppMode = 'production';
5616        if (!isBlank(thisApi.PayPalAccountMode))
5617            ppMode = thisApi.PayPalAccountMode;
5618
5619        paypal.Button.render({
5620            env: ppMode,
5621            style: {
5622                size: 'medium',
5623                color: 'blue',
5624                shape: 'rect',
5625                label: 'paypal',
5626                tagline: 'false'
5627            },
5628            payment: function (resolve, reject) {
5629
5630                _logger.Log('Initialize paypal payment');
5631
5632                let uiValidated = true;
5633
5634                if (typeof (thisApi.UIValidationCallBackFunction) == 'function')
5635                    uiValidated = thisApi.UIValidationCallBackFunction();
5636
5637                if (uiValidated) {
5638
5639                    return new paypal.Promise(function (resolve, reject) {
5640
5641                        let enablePayPalRec = 0;
5642                        if (thisApi.GetRecurringFrequencyIdFunction() > 0)
5643                            enablePayPalRec = 1;
5644
5645                        let presentmentCurrencyId = 0;
5646                        if (typeof (thisApi.GetCurrencyIdFunction) == 'function')
5647                            presentmentCurrencyId = thisApi.GetCurrencyIdFunction();
5648
5649                        const params = {
5650                            "ajaxParams": {
5651                                "PaypalGatewayId": thisApi.PayPalMerchantGatewayId,
5652                                "PaymentAmount": thisApi.GetChargeAmountFunction(),
5653                                "EnableRecurringBilling": enablePayPalRec,
5654                                "AppTypeId": 0, //deprecated (6.9.2023)
5655                                "AppId": thisApi.CharityEngineAppRecordId,
5656                                "PresentmentCurrencyId": presentmentCurrencyId
5657                            }
5658                        };
5659
5660                        _logger.Log('get authorization token params: ' + JSON.stringify(params));
5661
5662                        App.BackgroundPostJsonAsync('/api/PublicWebAppUIAjaxEndpoint/GetPaypalAuthorizationToken',
5663                            JSON.stringify(params),
5664                            function (result) {
5665
5666                                _logger.Log('result: ' + JSON.stringify(result));
5667
5668                                if (result != null && result.Success) {
5669
5670                                    let cbParam = {};
5671                                    cbParam.ApiVersion = 1;
5672                                    cbParam.PaymentSourceId = 1;
5673                                    cbParam.PayPalToken = result.PaypalToken;
5674                                    cbParam.ChargeAmount = result.ChargeAmount;
5675
5676                                    thisApi.FireTokenReceivedEvent(cbParam);
5677
5678                                    resolve(result.PaypalToken);
5679
5680                                }
5681                                else
5682                                    reject(result.ErrorMessage);
5683
5684                            },
5685                            function (error) {
5686                                _logger.Log('error: ' + error);
5687                                reject(error);
5688                            });
5689
5690                    });
5691
5692                }
5693                else {
5694
5695                    _logger.Log('Abort paypal payment becuase UI validation failed');
5696
5697                    return new paypal.Promise(function (resolve, reject) { reject('Required fields missing'); });
5698
5699                }
5700
5701            },
5702            onAuthorize: function (data) {
5703
5704                let cbParam = {};
5705                cbParam.PayPalGatewayPayerId = data.payerID;
5706
5707                thisApi.FirePaymentAuthorizedEvent(cbParam); // handlePaypalPaymentAuthorized();                        
5708
5709            },
5710            onCancel: function (data, actions) {
5711                _logger.Log('paypal payment cancelled');
5712            },
5713            onError: function (err) {
5714                console.error('paypal payment error', err);
5715            }
5716        }, '#' + thisApi.PayPalLegacyButtonContainerDomId);
5717
5718        if (thisApi.PaypalVenmoCheckOutEnabled) {
5719
5720            if (document.getElementById(thisApi.PayPalNewButtonContainerDomId) != null) {
5721
5722                _logger.Log('initialize latest check-out front-end API (paypal/venmo)');
5723
5724                //document.getElementById(thisApi.PayPalNewButtonContainerDomId).innerHTML = '<input type="button" value="New Paypal Button" onclick="alert(\'implment new paypal button\')" />';
5725
5726                //todo: implement new paypal button intialization
5727                const ppVmConfig = {
5728                    style: {
5729                        layout: 'horizontal',
5730                        color: 'blue',
5731                        shape: 'rect',
5732                        label: 'paypal',
5733                        height: 40,
5734                        tagline: false,
5735                        label: 'pay'
5736                    },
5737                    onInit(data, actions) {
5738
5739                        _logger.Log('new paypal button init event');
5740
5741                    },
5742                    onClick() {
5743
5744                        _logger.Log('new paypal button click event');
5745
5746                    },
5747                    createOrder() {
5748
5749                        _logger.Log('new paypal button create order event');
5750
5751                        let uiValidated = true;
5752
5753                        if (typeof (thisApi.UIValidationCallBackFunction) == 'function')
5754                            uiValidated = thisApi.UIValidationCallBackFunction();
5755
5756                        let presentmentCurrencyId = 0;
5757                        if (typeof (thisApi.GetCurrencyIdFunction) == 'function')
5758                            presentmentCurrencyId = thisApi.GetCurrencyIdFunction();
5759
5760                        if (uiValidated) {
5761
5762                            return new paypal.Promise(function (resolve, reject) {
5763
5764                                const params = {
5765                                    "ajaxParams": {
5766                                        "PayPalGatewayId": thisApi.PayPalMerchantGatewayId,                                        
5767                                        "PaymentAmount": thisApi.GetChargeAmountFunction(),
5768                                        "AppId": thisApi.CharityEngineAppRecordId,
5769                                        "PresentmentCurrencyId": presentmentCurrencyId
5770                                    }
5771                                };
5772                                 
5773                                _logger.Log('start payment params: ' + JSON.stringify(params));
5774
5775                                App.BackgroundPostJsonAsync('/api/PublicWebAppUIAjaxEndpoint/StartPayPalPayment',
5776                                    JSON.stringify(params),
5777                                    function (result) {
5778
5779                                        _logger.Log('result: ' + JSON.stringify(result));
5780
5781                                        if (result != null && result.Success) {
5782
5783                                            let cbParam = {};
5784                                            cbParam.ApiVersion = 2;
5785                                            cbParam.PaymentSourceId = result.PayPalPaymentSourceId;
5786                                            cbParam.PayPalToken = result.PayPalOrderId;
5787                                            cbParam.ChargeAmount = result.ChargeAmount;
5788
5789                                            thisApi.FireTokenReceivedEvent(cbParam);
5790
5791                                            resolve(result.PayPalOrderId);
5792
5793                                        }
5794                                        else
5795                                            reject(result.ErrorMessage);
5796
5797                                    },
5798                                    function (error) {
5799                                        _logger.Log('error: ' + error);
5800                                        reject(error);
5801                                    });
5802
5803                            });
5804
5805                        }
5806                        else {
5807
5808                            _logger.Log('Abort paypal payment becuase UI validation failed');
5809
5810                            return new paypal.Promise(function (resolve, reject) { reject('Required fields missing'); });
5811
5812                        }
5813
5814                    },
5815                    onApprove(data) {
5816
5817                        _logger.Log('new paypal button create order event');
5818
5819                        _logger.Log('new paypal sdk approve data: ' + JSON.stringify(data));
5820
5821                        let cbParam = {};
5822                        cbParam.PayPalGatewayPayerId = data.payerID;
5823
5824                        thisApi.FirePaymentAuthorizedEvent(cbParam);
5825
5826                    }
5827                };
5828
5829                //ppVmConfig.fundingSource = paypal.FUNDING.PAYPAL; //forces popup
5830
5831                paypal.Buttons(ppVmConfig).render('#' + thisApi.PayPalNewButtonContainerDomId);
5832
5833            }
5834
5835            if (thisApi.GetRecurringFrequencyIdFunction() <= 0 && thisApi.GetPaymentTypeLabelElement() != null) 
5836                thisApi.GetPaymentTypeLabelElement().innerHTML = "PayPal/Venmo";
5837            
5838        }
5839        else
5840            _logger.Log('skipping latest PayPal/Venmo check-out front-end initialization (configuration)');
5841
5842        thisApi.FireInitializedEvent();
5843
5844    };
5845
5846    return thisApi;
5847
5848}
5849
5850function getGooglePayApi(config) {
5851
5852    const thisApi = {};
5853
5854    thisApi.GooglePayEnabled = config.GooglePayEnabled;
5855
5856    thisApi.GooglePayDivId = config.GooglePayDivId;
5857    thisApi.GooglePayButtonContainerId = config.GooglePayButtonContainerId;
5858    thisApi.GooglePayEnvironment = config.GooglePayEnvironment;
5859    thisApi.GooglePayGateway = config.GooglePayGateway;
5860    thisApi.GooglePayGatewayMerchantID = config.GooglePayGatewayMerchantID;
5861
5862    thisApi.GooglePayMerchantID = config.GooglePayMerchantID;
5863    thisApi.OrganizationName = config.OrganizationName;
5864
5865    //UI interaction call backs
5866    thisApi.GetChargeAmountFunction = config.GetChargeAmountFunction;
5867    thisApi.GetRecurringAmountFunction = config.GetRecurringAmountFunction;
5868    thisApi.GetCurrencyCodeFunction = config.GetCurrencyCodeFunction;
5869    thisApi.GetCountryCodeFunction = config.GetCountryCodeFunction;
5870
5871    thisApi.UIValidationCallBackFunction = config.UIValidationCallBackFunction;
5872
5873    const _logger = getConsoleLogApi({ "LogSourceName": 'CE GooglePay Api' });
5874
5875    //https://developers.google.com/pay/api/web/reference/request-objects#PaymentDataRequest
5876    let baseRequest = {
5877        apiVersion: 2,
5878        apiVersionMinor: 0
5879    };
5880
5881    //https://developers.google.com/pay/api/web/reference/request-objects#CardParameters
5882    let baseCardPaymentMethod = {
5883        type: 'CARD',
5884        parameters: {
5885            allowedAuthMethods: ["PAN_ONLY"], //, "CRYPTOGRAM_3DS" NOTE: removed 3DS until we can verify GooglePay processor support and make configurable for one-time only (10.4.2023)
5886            allowedCardNetworks: ["AMEX", "DISCOVER", "INTERAC", "JCB", "MASTERCARD", "VISA"]
5887        }
5888    };
5889
5890    //https://developers.google.com/pay/api/web/reference/request-objects#CardParameters
5891    let cardPaymentMethod = Object.assign(
5892        {},
5893        baseCardPaymentMethod,
5894        {
5895            tokenizationSpecification: {
5896                type: 'PAYMENT_GATEWAY',
5897                parameters: {
5898                    'gateway': thisApi.GooglePayGateway,
5899                    'gatewayMerchantId': thisApi.GooglePayGatewayMerchantID
5900                }
5901            }
5902        }
5903    );
5904
5905    let googlePayClient = null;
5906
5907    thisApi.GetGooglePaymentsClient = function () { //    function getGooglePaymentsClient() {
5908
5909        if (googlePayClient == null)
5910            googlePayClient = new google.payments.api.PaymentsClient({ environment: thisApi.GooglePayEnvironment }); //https://developers.google.com/pay/api/web/reference/client#PaymentsClient
5911
5912        return googlePayClient;
5913
5914    }
5915
5916    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
5917
5918        CallBackManager.add('googlePayApiInitializedCallBack', callBackFunction, null);
5919
5920    };
5921
5922    thisApi.FireInitializedEvent = function () {
5923
5924        CallBackManager.fire('googlePayApiInitializedCallBack', {}, null);
5925
5926    };
5927
5928    thisApi.RegisterButtonAddedCallBack = function (callBackFunction) {
5929
5930        CallBackManager.add('googlePayApiButtonAddedCallBack', callBackFunction, null);
5931
5932    };
5933
5934    thisApi.FireButtonAddedEvent = function () {
5935
5936        CallBackManager.fire('googlePayApiButtonAddedCallBack', {}, null);
5937
5938    };
5939
5940    thisApi.RegisterPaymentAuthorizedCallBack = function (callBackFunction) {
5941
5942        CallBackManager.add('googlePayApiPaymentAuthorizedCallBack', callBackFunction, null);
5943
5944    };
5945
5946    thisApi.FirePaymentAuthorizedEvent = function (callBackParams) {
5947
5948        CallBackManager.fire('googlePayApiPaymentAuthorizedCallBack', callBackParams, null);
5949
5950    };
5951
5952    thisApi.IsGooglePayApiAvailable = function () {
5953        return (window.google && google.payments);
5954    }
5955     
5956    thisApi.GetContainerDivElement = function () {
5957
5958        let divCntElem = null;
5959
5960        if (!isBlank(thisApi.GooglePayDivId))
5961            divCntElem = document.getElementById(thisApi.GooglePayDivId);
5962
5963        return divCntElem;
5964
5965    };
5966
5967    thisApi.GetButtonContainerElement = function () {
5968
5969        let btnCntElem = null;
5970
5971        if (!isBlank(thisApi.GooglePayButtonContainerId))
5972            btnCntElem = document.getElementById(thisApi.GooglePayButtonContainerId);
5973
5974        return btnCntElem;
5975
5976    };
5977
5978    thisApi.GetButtonElement = function () {
5979
5980        let btnElem = document.querySelector('button.gpay-button');
5981
5982        return btnElem;
5983
5984    };
5985
5986    thisApi.ShowButton = function () {
5987
5988        if (thisApi.GetContainerDivElement() != null)
5989            thisApi.GetContainerDivElement().show();
5990
5991        if (thisApi.GetButtonElement() != null)
5992            thisApi.GetButtonElement().show();
5993
5994    };
5995
5996    thisApi.HideButton = function () {
5997
5998        if (thisApi.GetContainerDivElement() != null)
5999            thisApi.GetContainerDivElement().vanish();
6000
6001        if (thisApi.GetButtonElement() != null)
6002            thisApi.GetButtonElement().vanish();
6003
6004    };
6005
6006    thisApi.GetGoogleIsReadyToPayRequest = function () {
6007
6008        return Object.assign(
6009            {},
6010            baseRequest,
6011            {
6012                allowedPaymentMethods: [baseCardPaymentMethod]
6013            }
6014        );
6015
6016    };
6017
6018    thisApi.GetChargeAmount = function () {
6019
6020        let chargeAmt = 0.00;
6021
6022        if (typeof (thisApi.GetChargeAmountFunction) == 'function')
6023            chargeAmt = thisApi.GetChargeAmountFunction();
6024
6025        return chargeAmt;
6026
6027    };
6028
6029    thisApi.GetRecurringAmount = function () {
6030
6031        let recurringAmt = 0.00;
6032
6033        if (typeof (thisApi.GetRecurringAmountFunction) == 'function')
6034            recurringAmt = thisApi.GetRecurringAmountFunction();
6035
6036        return recurringAmt;
6037
6038    };
6039
6040    //Initialize
6041    thisApi.Initialize = function () {
6042
6043        _logger.Log('initialize started');
6044
6045        let btnCntElem = thisApi.GetButtonContainerElement();
6046
6047        if (btnCntElem != null) {
6048
6049            // prefetchGooglePaymentData(); // @todo prefetch payment data to improve performance after confirming site functionality
6050
6051            let gpc = thisApi.GetGooglePaymentsClient();
6052
6053            gpc.isReadyToPay(thisApi.GetGoogleIsReadyToPayRequest())
6054                .then(function (response) {
6055
6056                    _logger.Log('initialize GooglePay button returned result: ' + response.result);
6057
6058                    if (response.result) {
6059
6060                        _logger.Log('add GooglePay button to DOM');
6061
6062                        let gpButton =
6063                            gpc.createButton({
6064                                buttonType: 'donate',
6065                                onClick: function () {
6066
6067                                    _logger.Log('GooglePay native button clicked');
6068
6069                                    let uiValidated = true;
6070
6071                                    if (typeof (thisApi.UIValidationCallBackFunction) == 'function')
6072                                        uiValidated = thisApi.UIValidationCallBackFunction();
6073
6074                                    if (uiValidated) {
6075
6076                                        _logger.Log('Hangle GooglePay button click using host: ' + gl_ce_pageHostName);
6077
6078                                        let paymentDataRequest = Object.assign({}, baseRequest);
6079
6080                                        paymentDataRequest.allowedPaymentMethods = [cardPaymentMethod];
6081
6082                                        //https://developers.google.com/pay/api/web/guides/test-and-deploy/integration-checklist
6083                                        paymentDataRequest.merchantInfo = {
6084                                            merchantId: thisApi.GooglePayMerchantID,
6085                                            merchantName: thisApi.OrganizationName,
6086                                            merchantOrigin: gl_ce_pageHostName
6087                                        };
6088
6089                                        let totalAmount = thisApi.GetChargeAmount();
6090
6091                                        if (totalAmount <= 0 && thisApi.GetRecurringAmount() > 0)
6092                                            totalAmount = thisApi.GetRecurringAmount();
6093
6094                                        paymentDataRequest.transactionInfo = {
6095                                            countryCode: thisApi.GetCountryCodeFunction(),
6096                                            currencyCode: thisApi.GetCurrencyCodeFunction(),
6097                                            totalPriceStatus: 'FINAL',
6098                                            totalPrice: totalAmount.toString()
6099                                        };
6100
6101                                        gpc.loadPaymentData(paymentDataRequest)
6102                                            .then(function (paymentData) {
6103
6104                                                // handle the response
6105                                                let paymentToken = paymentData.paymentMethodData.tokenizationData.token;
6106                                                let payloadToken = JSON.parse(paymentToken);
6107
6108                                                let params = {
6109                                                    "ajaxParams": {
6110                                                        "signature": payloadToken.signature,
6111                                                        "protocolVersion": payloadToken.protocolVersion,
6112                                                        "signedMessage": payloadToken.signedMessage,
6113                                                        "intermediateSigningKey": payloadToken.intermediateSigningKey
6114                                                    }
6115                                                };
6116
6117                                                App.BackgroundPostJsonAsync('/api/PublicWebAppUIAjaxEndpoint/GetGooglePayDecrytedPayoadToken',
6118                                                    JSON.stringify(params),
6119                                                    function (result) {
6120
6121                                                        _logger.Log
6121('result: ' + JSON.stringify(result));
6122
6123                                                        if (result != null && result.Success) {
6124
6125                                                            let paymentData = JSON.parse(result.DecrypedMessage);
6126
6127                                                            _logger.Log(paymentData)
6128
6129                                                            thisApi.FirePaymentAuthorizedEvent(paymentData); //handleGooglePayPaymentAuthorized(paymentData);
6130
6131                                                        }
6132                                                        else
6133                                                            _logger.Log('result: ' + JSON.stringify(result));
6134
6135                                                    },
6136                                                    function (error) {
6137                                                        _logger.Log('error: ' + error);
6138                                                    });
6139
6140                                            })
6141                                            .catch(function (err) {
6142                                                console.error(err);
6143                                            });
6144
6145                                    }
6146                                    else
6147                                        _logger.Log('GooglePay native button click aborted due to UI validation failure');
6148
6149                                },
6150                                allowedPaymentMethods: [baseCardPaymentMethod]
6151                            });
6152
6153                        if (beginsWith(thisApi.GooglePayButtonContainerId, 'fieldset_')) {
6154
6155                            let buttonContainer = document.createElement("div");
6156                            buttonContainer.id = 'googlePayButtonContainer';
6157                            buttonContainer.appendChild(gpButton);
6158
6159                            btnCntElem.appendChild(buttonContainer);
6160
6161                        }
6162                        else
6163                            btnCntElem.appendChild(gpButton);
6164
6165                        thisApi.FireButtonAddedEvent(); //tryInitiateGooglePayThemeCallBack();
6166
6167                    }
6168                    else
6169                        _logger.Log('skip adding GooglePay button due to failed ready request');
6170
6171                })
6172                .catch(function (err) {
6173                    console.error('initialize GooglePay button failed with error: ' + err); // show error in developer console for debugging
6174                });
6175
6176        }
6177
6178        thisApi.FireInitializedEvent();
6179
6180    };
6181
6182    return thisApi;
6183
6184}
6185
6186function getApplePayApi(config) {
6187
6188    const thisApi = {};
6189
6190    thisApi.ApplePayEnabled = config.ApplePayEnabled;
6191
6192    thisApi.ApplePayButtonDomId = 'ceApplePay';
6193
6194    thisApi.ApplePayDivId = config.ApplePayDivId;
6195    thisApi.ApplePayButtonContainerId = config.ApplePayButtonContainerId;
6196
6197    thisApi.OrganizationName = config.OrganizationName;
6198    thisApi.UserCenterUrl = config.UserCenterUrl;
6199
6200    //UI interaction call backs
6201    thisApi.GetChargeAmountFunction = config.GetChargeAmountFunction;
6202    thisApi.GetRecurringAmountFunction = config.GetRecurringAmountFunction;
6203    thisApi.GetCurrencyCodeFunction = config.GetCurrencyCodeFunction;
6204    thisApi.GetCountryCodeFunction = config.GetCountryCodeFunction;
6205
6206    thisApi.UIValidationCallBackFunction = config.UIValidationCallBackFunction;
6207
6208    const _logger = getConsoleLogApi({ "LogSourceName": 'CE ApplePay Api' });
6209
6210    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
6211
6212        CallBackManager.add('applePayApiInitializedCallBack', callBackFunction, null);
6213
6214    };
6215
6216    thisApi.FireInitializedEvent = function () {
6217
6218        CallBackManager.fire('applePayApiInitializedCallBack', {}, null);
6219
6220    };
6221
6222    thisApi.RegisterPaymentAuthorizedCallBack = function (callBackFunction) {
6223
6224        CallBackManager.add('applePayApiPaymentAuthorizedCallBack', callBackFunction, null);
6225
6226    };
6227
6228    thisApi.FirePaymentAuthorizedEvent = function (callBackParams) {
6229
6230        CallBackManager.fire('applePayApiPaymentAuthorizedCallBack', callBackParams, null);
6231
6232    };
6233
6234    thisApi.IsApplePayApiAvailable = function () {
6235
6236        let retVal = false
6237
6238        try {
6239            if (window.top.ApplePaySession && window.top.ApplePaySession.canMakePayments() && thisApi.GetButtonElement() != null)
6240                retVal = true;
6241        }
6242        catch (err) {
6243            _logger.Log('IsApplePayApiAvailable error: ' + err.message);
6244        }
6245
6246        return retVal;
6247
6248    }
6249
6250    thisApi.GetButtonElement = function () {
6251
6252        let btnElem = null;
6253
6254        if (!isBlank(thisApi.ApplePayButtonDomId))
6255            btnElem = document.getElementById(thisApi.ApplePayButtonDomId);
6256
6257        return btnElem;
6258
6259    };
6260
6261    thisApi.GetContainerDivElement = function () {
6262
6263        let divCntElem = null;
6264
6265        if (!isBlank(thisApi.ApplePayDivId))
6266            divCntElem = document.getElementById(thisApi.ApplePayDivId);
6267
6268        return divCntElem;
6269
6270    };
6271
6272    thisApi.GetButtonContainerElement = function () {
6273
6274        let btnCntElem = null;
6275
6276        if (!isBlank(thisApi.ApplePayButtonContainerId))
6277            btnCntElem = document.getElementById(thisApi.ApplePayButtonContainerId);
6278
6279        return btnCntElem;
6280
6281    };
6282
6283    thisApi.ShowButton = function () {
6284
6285        if (thisApi.GetContainerDivElement() != null)
6286            thisApi.GetContainerDivElement().show();
6287
6288        if (thisApi.GetButtonElement() != null)
6289            thisApi.GetButtonElement().show();
6290
6291    };
6292
6293    thisApi.HideButton = function () {
6294
6295        if (thisApi.GetContainerDivElement() != null)
6296            thisApi.GetContainerDivElement().vanish();
6297
6298        if (thisApi.GetButtonElement() != null)
6299            thisApi.GetButtonElement().vanish();
6300
6301    };
6302
6303    thisApi.GetChargeAmount = function () {
6304
6305        let chargeAmt = 0.00;
6306
6307        if (typeof (thisApi.GetChargeAmountFunction) == 'function')
6308            chargeAmt = thisApi.GetChargeAmountFunction();
6309
6310        return chargeAmt;
6311
6312    };
6313
6314    thisApi.GetRecurringAmount = function () {
6315
6316        let recurringAmt = 0.00;
6317
6318        if (typeof (thisApi.GetRecurringAmountFunction) == 'function')
6319            recurringAmt = thisApi.GetRecurringAmountFunction();
6320
6321        return recurringAmt;
6322
6323    };
6324
6325    thisApi.GetApplePayTransactionObject = function () {
6326
6327        let apto = {};
6328
6329        apto.countryCode = thisApi.GetCountryCodeFunction();
6330        apto.currencyCode = thisApi.GetCurrencyCodeFunction();
6331        apto.merchantCapabilities = ["supports3DS"];
6332        apto.supportedNetworks = ["visa", "masterCard", "amex", "discover"];
6333
6334        let chargeAmt = thisApi.GetChargeAmount();
6335        if (chargeAmt > 0) {
6336
6337            _logger.Log('populate ApplePay charge amount transaction info (' + chargeAmt + ')');
6338
6339            apto.total = {
6340                "label": thisApi.OrganizationName,
6341                "type": "final",
6342                "amount": chargeAmt
6343            };
6344
6345        }
6346        else
6347            _logger.Log('no charge amount for ApplePay transaction info');
6348
6349        let recurringAmt = thisApi.GetRecurringAmount();
6350        if (recurringAmt > 0) {
6351
6352            _logger.Log('populate ApplePay recurring transaction info (' + recurringAmt + ')');
6353
6354            apto.recurringPaymentRequest = {
6355                "paymentDescription": "Recurring donation.",
6356                "regularBilling": {
6357                    "label": "Recurring",
6358                    "amount": recurringAmt,
6359                    "paymentTiming": "recurring",
6360                    "recurringPaymentStartDate": getDateTimeISO()
6361                },
6362                "billingAgreement": "You agree to allow our organization to bill your ApplePay account on a recurring basis for the amount you selected until you cancel the donation.",
6363                "managementURL": thisApi.UserCenterUrl //todo: needs to be client-specific
6364                //"tokenNotificationURL": "" //https://developer.apple.com/documentation/apple_pay_on_the_web/applepayrecurringpaymentrequest/3955958-tokennotificationurl
6365            };
6366
6367        }
6368        else
6369            _logger.Log('no recurring amount for ApplePay transaction info');
6370
6371        //if (typeof (callBackFunction) == 'function')
6372        //    callBackFunction({});
6373
6374        return apto;
6375
6376    };
6377
6378    //Initialize
6379    thisApi.Initialize = function () {
6380
6381        _logger.Log('initialize started');
6382
6383        let btnCntElem = thisApi.GetButtonContainerElement();
6384
6385        if (btnCntElem != null) {
6386
6387            //create apple pay button
6388            let appelepaybutton = document.createElement("div");
6389            appelepaybutton.id = thisApi.ApplePayButtonDomId;
6390            appelepaybutton.className = "apple-pay-button apple-pay-button-text-donate";
6391            appelepaybutton.lang = "en";
6392
6393            // Set the onClick listener on the Apple Pay button
6394            appelepaybutton.addEventListener('click', function () {
6395
6396                _logger.Log('ApplePay button clicked');
6397
6398                let uiValidated = true;
6399
6400                if (typeof (thisApi.UIValidationCallBackFunction) == 'function')
6401                    uiValidated = thisApi.UIValidationCallBackFunction();
6402
6403                if (uiValidated && window.frame && window.frame !== window.top) {
6404
6405                    window.parent.postMessage({ type: 'ce_fireEmbeddedApplePayButtonClickedEvent' }, '*');
6406
6407                }
6408                else if (uiValidated) {
6409
6410                    let request = thisApi.GetApplePayTransactionObject();
6411
6412                    let applePaySession = new ApplePaySession(6, request);
6413
6414                    // This is the first event that Apple triggers. Here you need to validate the Apple Pay Session from your Back-End
6415                    applePaySession.onvalidatemerchant = function (event) {
6416
6417                        _logger.Log('handle IOS merchant validation (' + event.validationURL + ')');
6418
6419                        let params = {
6420                            "ajaxParams": {
6421                                "appleUrl": event.validationURL,
6422                                "initiativeContext": gl_ce_pageHostName
6423                            }
6424                        };
6425
6426                        App.BackgroundPostJsonAsync('/api/PublicWebAppUIAjaxEndpoint/ApplePayMerchantValidation',
6427                            JSON.stringify(params),
6428                            function (result) {
6429
6430                                _logger.Log('ApplePayMerchantValidation result: ' + JSON.stringify(result));
6431
6432                                try {
6433                                    applePaySession.completeMerchantValidation(JSON.parse(result));
6434                                }
6435                                catch (ex) {
6436                                    _logger.Log('ApplePaySession completeMerchantValidation exception: ' + ex);
6437                                }
6438
6439                            },
6440                            function (error) {
6441                                _logger.Log('ApplePayMerchantValidation error: ' + error);
6442                            });
6443
6444                    };
6445
6446                    applePaySession.onshippingcontactselected = function (event) {
6447
6448                        _logger.Log('handle IOS shipping contact selected');
6449
6450                        // populate with the availbale shipping methods for the region (Apple will tell you the region). 
6451                        let shipping = {};
6452
6453                        if (config.shop.shop_localisation.countryCode == 'GB')
6454                            shipping = { methods: config.shipping.GB_region };
6455                        else
6456                            shipping = { methods: config.shipping.WORLDWIDE_region };
6457
6458                        let newTotal = {
6459                            type: 'final',
6460                            label: config.shop.shop_name,
6461                            amount: (parseFloat(config.shop.product_price) + parseFloat(shipping.methods[0].amount)).toFixed(2)
6462                        };
6463
6464                        let newLineItems = [
6465                            {
6466                                type: 'final',
6467                                label: 'Subtotal',
6468                                amount: config.shop.product_price
6469                            },
6470                            {
6471                                type: 'final',
6472                                label: shipping.methods[0].label,
6473                                amount: shipping.methods[0].amount
6474                            }
6475                        ];
6476
6477                        applePaySession.completeShippingContactSelection(
6478                            ApplePaySession.STATUS_SUCCESS,
6479                            shipping.methods,
6480                            newTotal,
6481                            newLineItems
6482                        );
6483
6484                    };
6485
6486                    applePaySession.onshippingmethodselected = function (event) {
6487
6488                        _logger.Log('handle IOS shipping method selected');
6489
6490                        let newTotal = {
6491                            type: 'final',
6492                            label: config.shop.shop_name,
6493                            amount: (parseFloat(config.shop.product_price) + parseFloat(event.shippingMethod.amount)).toFixed(2)
6494                        };
6495
6496                        let newLineItems = [
6497                            {
6498                                type: 'final',
6499                                label: 'Subtotal',
6500                                amount: config.shop.product_price
6501                            },
6502                            {
6503                                type: 'final',
6504                                label: event.shippingMethod.label,
6505                                amount: event.shippingMethod.amount
6506                            }
6507                        ];
6508
6509                        applePaySession.completeShippingMethodSelection(
6510                            ApplePaySession.STATUS_SUCCESS,
6511                            newTotal,
6512                            newLineItems
6513                        );
6514
6515                    };
6516
6517                    applePaySession.onpaymentauthorized = function (event) {
6518
6519                        _logger.Log('handle IOS payment authorized');
6520
6521                        let payloadToken = event.payment.token;
6522
6523                        let params = {
6524                            "ajaxParams": {
6525                                "paymentData": payloadToken.paymentData,
6526                                "paymentMethod": payloadToken.paymentMethod,
6527                                "transactionIdentifier": payloadToken.transactionIdentifier
6528                            }
6529                        };
6530
6531                        App.BackgroundPostJsonAsync('/api/PublicWebAppUIAjaxEndpoint/ApplePayDecrytedPayoadToken',
6532                            JSON.stringify(params),
6533                            function (result) {
6534
6535                                _logger.Log('ApplePayDecrytedPayoadToken result: ' + result);
6536
6537                                if (result != null && result.Success) {
6538
6539                                    _logger.Log('ApplePayDecrytedPayoadToken DecrypedMessage: ' + result.DecrypedMessage);
6540
6541                                    let apPaymentKeys = JSON.parse(result.DecrypedMessage);
6542
6543                                    _logger.Log('DecrypedMessage.applicationPrimaryAccountNumber: ' + apPaymentKeys.applicationPrimaryAccountNumber);
6544                                    _logger.Log('DecrypedMessage.applicationExpirationDate: ' + apPaymentKeys.applicationExpirationDate);
6545                                    _logger.Log('DecrypedMessage.paymentData: ' + apPaymentKeys.paymentData);
6546                                    _logger.Log('DecrypedMessage.paymentData.onlinePaymentCryptogram: ' + apPaymentKeys.paymentData.onlinePaymentCryptogram);
6547                                    _logger.Log('DecrypedMessage.paymentData.eciIndicator: ' + apPaymentKeys.paymentData.eciIndicator);
6548
6549                                    applePaySession.completePayment(ApplePaySession.STATUS_SUCCESS);
6550
6551                                    thisApi.HideButton();
6552
6553                                    //_logger.Log('payloadToken.PaymentMethod.type: ' + payloadToken.PaymentMethod.type);
6554                                    //_logger.Log('payloadToken.PaymentMethod.displayName: ' + payloadToken.PaymentMethod.displayName);
6555                                    //_logger.Log('payloadToken.PaymentMethod.network: ' + payloadToken.PaymentMethod.network);
6556
6557                                    let cbParam = {};
6558                                    cbParam.PaymentMethod = payloadToken.paymentMethod.type;
6559                                    cbParam.CardType = payloadToken.paymentMethod.network;
6560                                    cbParam.CardDisplayName = payloadToken.paymentMethod.displayName;
6561
6562                                    cbParam.CardType_Id = 0;
6563
6564                                    //parse card type id
6565                                    switch (cbParam.CardType.toLowerCase()) {
6566                                        case 'visa':
6567                                            cbParam.CardType_Id = 1;
6568                                            break;
6569                                        case 'mastercard':
6570                                            cbParam.CardType_Id = 2;
6571                                            break;
6572                                        case 'amex':
6573                                            cbParam.CardType_Id = 3;
6574                                            break;
6575                                        case 'discover':
6576                                            cbParam.CardType_Id = 4;
6577                                            break;
6578                                    }
6579
6580                                    _logger.Log('cbParam.CardType_Id: ' + cbParam.CardType_Id);
6581
6582                                    cbParam.ApplePayToken = apPaymentKeys.applicationPrimaryAccountNumber;
6583
6584                                    //may be necessary for test apple pay wallets -- currently they are returning null for the ECI indicator
6585                                    if (apPaymentKeys.paymentData.eciIndicator != null) {
6586                                        cbParam.ApplePayTokenEciIndicator = apPaymentKeys.paymentData.eciIndicator;
6587                                    }
6588                                    else {
6589                                        switch (cbParam.CardType.toLowerCase()) {
6590                                            case 'visa':
6591                                                cbParam.ApplePayTokenEciIndicator = 7;
6592                                                break;
6593                                            case 'mastercard':
6594                                                cbParam.ApplePayTokenEciIndicator = 5;
6595                                                break;
6596                                            case 'amex':
6597                                                cbParam.ApplePayTokenEciIndicator = 5;
6598                                                break;
6599                                            case 'discover':
6600                                                cbParam.ApplePayTokenEciIndicator = 5;
6601                                                break;
6602                                        }
6603                                    }
6604
6605                                    cbParam.ExpirationMonth = '';
6606                                    cbParam.ExpirationYear = '';
6607
6608                                    if (!isBlank(apPaymentKeys.applicationExpirationDate) && apPaymentKeys.applicationExpirationDate.length == 6) {
6609                                        cbParam.ExpirationMonth = apPaymentKeys.applicationExpirationDate.substring(2, 4);
6610                                        cbParam.ExpirationYear = apPaymentKeys.applicationExpirationDate.substring(0, 2);
6611                                    }
6612
6613                                    _logger.Log('cbParam.ExpirationMonth: ' + cbParam.ExpirationMonth);
6614                                    _logger.Log('cbParam.ExpirationYear: ' + cbParam.ExpirationYear);
6615
6616                                    cbParam.ApplePayCryptogram = apPaymentKeys.paymentData.onlinePaymentCryptogram;
6617
6618                                    thisApi.FirePaymentAuthorizedEvent(cbParam);
6619
6620                                }
6621                                else
6622                                    applePaySession.completePayment(ApplePaySession.STATUS_FAILURE);
6623
6624                            },
6625                            function (error) {
6626                                _logger.Log('ApplePayDecrytedPayoadToken error: ' + error);
6627                                applePaySession.completePayment(ApplePaySession.STATUS_FAILURE);
6628                            });
6629
6630                    };
6631
6632                    _logger.Log('start ApplePay session');
6633
6634                    //begin IOS SESSION
6635                    applePaySession.begin();
6636
6637                }
6638                else
6639                    _logger.Log('ApplePay session skipped due to UI validation error');
6640
6641            });
6642
6643            if (beginsWith(thisApi.ApplePayButtonContainerId, 'fieldset_')) {
6644
6645                let applePayButtonContainer = document.createElement("div");
6646                applePayButtonContainer.id = 'applePayButtonContainer';
6647                applePayButtonContainer.appendChild(appelepaybutton);
6648
6649                btnCntElem.appendChild(applePayButtonContainer);
6650
6651            }
6652            else
6653                btnCntElem.appendChild(appelepaybutton);
6654
6655            //this is a bug - why is the inner API showing the button, shouldn't the consumer?
6656            if (thisApi.IsApplePayApiAvailable())
6657                thisApi.ShowButton();
6658            else
6659                thisApi.HideButton();
6660
6661        }
6662
6663        thisApi.FireInitializedEvent();
6664
6665    };
6666
6667    return thisApi;
6668
6669}
6670
6671function getChariotDafPayApi(config) {
6672
6673    const thisApi = {};
6674
6675    thisApi.DafPayEnabled = config.DafPayEnabled;
6676    thisApi.DafPayButtonContainerDomId = config.DafPayButtonContainerDomId;
6677    thisApi.DafPayClientConnectId = config.DafPayClientConnectId;
6678    thisApi.GetChargeAmountFunction = config.GetChargeAmountFunction;
6679    thisApi.GetRecurringFrequencyIdFunction = config.GetRecurringFrequencyIdFunction;
6680    thisApi.UIValidationCallBackFunction = config.UIValidationCallBackFunction;
6681    thisApi.GetContactInformationFunction = config.GetContactInformationFunction;    
6682
6683    let dafPayButtonContainer = 'dafPayButtonContainer';
6684    let dafPayButtonId = 'dafPayButton';
6685
6686    const _logger = getConsoleLogApi({ "LogSourceName": 'CE DafPay Api' });
6687
6688    thisApi.GetButtonElement = function () {
6689        return document.getElementById(dafPayButtonId);
6690    };
6691
6692    thisApi.GetButtonMasterContainerElement = function () {
6693
6694        let btnElem = document.getElementById(thisApi.DafPayButtonContainerDomId);
6695
6696        return btnElem;
6697
6698    };
6699
6700    thisApi.GetButtonContainerElement = function () {
6701
6702        let btnElem = document.getElementById(dafPayButtonContainer);
6703
6704        return btnElem;
6705
6706    };
6707
6708    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
6709
6710        CallBackManager.add('dafPayApiInitializedCallBack', callBackFunction, null);
6711
6712    };
6713
6714    thisApi.FireInitializedEvent = function () {
6715
6716        CallBackManager.fire('dafPayApiInitializedCallBack', {}, null);
6717
6718    };
6719
6720    thisApi.RegisterButtonClickedCallBack = function (callBackFunction) {
6721
6722        CallBackManager.add('dafPayApiButtonClickedCallBack', callBackFunction, null);
6723
6724    };
6725
6726    thisApi.FireButtonClickedEvent = function (callBackParams) {
6727
6728        CallBackManager.fire('dafPayApiButtonClickedCallBack', callBackParams, null);
6729
6730    };
6731
6732    thisApi.RegisterPaymentAuthorizedCallBack = function (callBackFunction) {
6733
6734        CallBackManager.add('dafPayApiPaymentAuthorizedCallBack', callBackFunction, null);
6735
6736    };
6737
6738    thisApi.FirePaymentAuthorizedEvent = function (callBackParams) {
6739
6740        CallBackManager.fire('dafPayApiPaymentAuthorizedCallBack', callBackParams, null);
6741
6742    };
6743
6744    thisApi.RegisterPaymentFailedCallBack = function (callBackFunction) {
6745
6746        CallBackManager.add('dafPayApiPaymentFailedCallBack', callBackFunction, null);
6747
6748    };
6749
6750    thisApi.FirePaymentFailedEvent = function (callBackParams) {
6751
6752        CallBackManager.fire('dafPayApiPaymentFailedCallBack', callBackParams, null);
6753
6754    };
6755
6756    thisApi.ShowButton = function () {
6757
6758        //if (thisApi.GetButtonMasterContainerElement() != null)
6759        //    thisApi.GetButtonMasterContainerElement().show();
6760
6761        if (thisApi.GetButtonContainerElement() != null)
6762            thisApi.GetButtonContainerElement().show();
6763
6764        if (thisApi.GetButtonElement() != null)
6765            thisApi.GetButtonElement().show(); //this has no effect is it is a fake tag
6766
6767    };
6768
6769    thisApi.HideButton = function () {
6770
6771        //if (thisApi.GetButtonMasterContainerElement() != null)
6772        //    thisApi.GetButtonMasterContainerElement().vanish();
6773
6774        if (thisApi.GetButtonContainerElement() != null)
6775            thisApi.GetButtonContainerElement().vanish();
6776
6777        if (thisApi.GetButtonElement() != null)
6778            thisApi.GetButtonElement().vanish(); //this has no effect is it is a fake tag
6779
6780    };
6781
6782    //Initialize
6783    thisApi.Initialize = function () {
6784
6785        _logger.Log('initialize started');
6786
6787        let btnContainer = thisApi.GetButtonMasterContainerElement();
6788        if (btnContainer != null) {
6789
6790            if (!isBlank(thisApi.DafPayClientConnectId)) {
6791
6792                _logger.Log('configure DafPay button for connect id ' + thisApi.DafPayClientConnectId + ' in ' + thisApi.DafPayButtonContainerDomId);
6793
6794                let dpBtnCont = document.createElement('div');
6795                dpBtnCont.id = dafPayButtonContainer;
6796                dpBtnCont.setAttribute('style', 'display:none;'); //hide by default
6797
6798                //<chariot-connect id="chariot" cid="GENERATED_CONNECT_IDENTIFIER"></chariot-connect>
6799                let tag = document.createElement('chariot-connect');
6800                tag.id = dafPayButtonId;
6801                tag.setAttribute('cid', thisApi.DafPayClientConnectId);
6802                tag.setAttribute('style', 'width:auto;margin:0 auto;');
6803
6804                dpBtnCont.appendChild(tag);
6805
6806                btnContainer.appendChild(dpBtnCont);
6807
6808                let dpBtn = thisApi.GetButtonElement();
6809
6810                if (dpBtn != null) {
6811
6812                    //optional event if needed to detect initialization
6813                    dpBtn.addEventListener('CHARIOT_INIT', () => {
6814
6815                        _logger.Log('DafPay init event fired');
6816
6817                    });
6818
6819                    //this is the key listener that fires back when a user either completes the DafPay app
6820                    dpBtn.addEventListener('CHARIOT_SUCCESS', ({ detail }) => {
6821
6822                        _logger.Log('DafPay user completion fired: ' + JSON.stringify(detail));
6823
6824                        //process transaction
6825
6826                        let cbParam = {};
6827                        cbParam.DafPayResponse = detail;
6828
6829                        thisApi.FirePaymentAuthorizedEvent(cbParam);
6830
6831                    });
6832
6833                    //this is the key listener that fires back when a user abandons the DafPay app
6834                    dpBtn.addEventListener('CHARIOT_EXIT', ({ detail }) => {
6835
6836                        _logger.Log('DafPay user abandon fired: ' + JSON.stringify(detail));
6837
6838                        //do nothing for now
6839
6840                        let cbParam = {};
6841                        cbParam.DafPayResponse = detail;
6842
6843                        thisApi.FirePaymentFailedEvent(cbParam);
6844
6845                    });
6846
6847                    //consider loading optional donor info via call backs
6848                    dpBtn.onDonationRequest(async () => {
6849
6850                        _logger.Log('DafPay button clicked (check out starting)');
6851
6852                        thisApi.FireButtonClickedEvent({});
6853
6854                        let uiValidated = true;
6855
6856                        if (typeof (thisApi.UIValidationCallBackFunction) == 'function')
6857                            uiValidated = thisApi.UIValidationCallBackFunction();
6858
6859                        let amtRaw = thisApi.GetChargeAmountFunction();
6860
6861                        if (typeof amtRaw === 'string')
6862                            amtRaw = Number(stripCurrencyFormatting(amtRaw));
6863
6864                        let retVal = {
6865                            "amount": parseFloat(amtRaw.toFixed(2).replace('.', '')),
6866                            "firstName": thisApi.GetContactInformationFunction().DonorFirstName(),
6867                            "lastName": thisApi.GetContactInformationFunction().DonorLastName(),
6868                            "email": thisApi.GetContactInformationFunction().DonorEmail(),
6869                            "phone": thisApi.GetContactInformationFunction().DonorPhone(),
6870                            "address": {
6871                                "line1": thisApi.GetContactInformationFunction().DonorAddress1(),
6872                                "line2": thisApi.GetContactInformationFunction().DonorAddress2(),
6873                                "city": thisApi.GetContactInformationFunction().DonorCity(),
6874                                "state": thisApi.GetContactInformationFunction().DonorState(),
6875                                "postalCode": thisApi.GetContactInformationFunction().DonorZipCode()
6876                            }
6877                        };
6878
6879                        if (!uiValidated)
6880                            retVal = false;
6881
6882                        return retVal; //false will cancel the donation flow
6883
6884                    });
6885
6886                }
6887                else
6888                    _logger.Log('CRITICAL EXCEPTION accessing DafPay button after initialization');
6889
6890            }
6891            else
6892                _logger.Log('unable to initialize DafPay button because connect id is missing');
6893
6894        }
6895        else
6896            _logger.Log('unable to initialize DafPay button because container is not valid: ' + thisApi.DafPayButtonContainerDomId);
6897
6898        thisApi.FireInitializedEvent();
6899
6900    };
6901
6902    return thisApi;
6903
6904}
6905
6906/*Web Forms Api*/
6907
6908function getFormApi(config) {
6909
6910    const thisApi = {};
6911
6912    thisApi.Form_Id = config.Form_Id;
6913    thisApi.FormDomId = config.FormDomId;
6914    thisApi.HeaderMessagesContainerDomId = config.HeaderMessagesContainerDomId;
6915    thisApi.SubmitButtonDomId = config.SubmitButtonDomId;
6916    //thisApi.BackButtonDomId = config.BackButtonDomId;
6917    thisApi.ButtonsContainerDomId = config.ButtonsContainerDomId;
6918
6919    thisApi.AutoInitialize = (config.AutoInitialize != null) ? config.AutoInitialize : true;
6920
6921    thisApi.PayPalApi = config.PayPalApi;
6922    thisApi.ApplePayApi = config.ApplePayApi;
6923    thisApi.GooglePayApi = config.GooglePayApi;
6924    thisApi.DafPayApi = config.DafPayApi;
6925
6926    thisApi.PromoCodeFieldApi = null;
6927    thisApi.ContactPostalCodeFieldApi = null;
6928    thisApi.ContactSingleLineAddressApi = null;
6929    thisApi.TributeSingleLineAddressApi = null;
6930
6931    thisApi.OneClickCheckOutApi = null;
6932
6933    thisApi.TitleContainerDomId = '';
6934    thisApi.HeaderContainerDomId = '';
6935    thisApi.FooterContentDomId = '';
6936
6937    //Core Third-Party Payment processing tracking global variables   
6938    //let _walletTypeId = 0;
6939    let _payPalTokenId = '';
6940    let _payPalGatewayPayerId = '';
6941    let _payPalAmountChargedResponse = '';
6942
6943    let _walletToken = '';
6944    let _walletCreditCardType_id = 0;
6945    let _walletEciIndicator = '';
6946    let _tokenExpirationMonth = '';
6947    let _tokenExpirationYear = '';
6948    let _tokenCryptogram = '';
6949
6950    let _dafPayWorkflowSessionId = '';
6951
6952    const _logger = getConsoleLogApi({ "LogSourceName": 'CE Web Forms 2.0 Api' });
6953
6954    //Core Form Meta Properties & Framework
6955    //******************************************
6956
6957    let _trans_id = 0;
6958    Object.defineProperty(thisApi, 'Transaction_Id', {
6959        get: function () {
6960            return _trans_id;
6961        }
6962    });
6963
6964    let _transAmount = 0.00;
6965    Object.defineProperty(thisApi, 'ProcessedPaymentAmount', {
6966        get: function () {
6967            return _transAmount;
6968        }
6969    });
6970
6971    //IMPORTANT: need new theme API framework and it needs a contract to communicate to the form API
6972    thisApi.ThemeRunning = false;
6973
6974    Object.defineProperty(thisApi, 'Id', {
6975        get: function () {
6976            return Form_Id;
6977        }
6978    });
6979
6980    const _getFormName = function () { return WebFormContext.Form.Name; };
6981    Object.defineProperty(thisApi, 'Name', {
6982        get: function () {
6983            return _getFormName();
6984        }
6985    });
6986
6987    const _getFormElement = function () { return document.getElementById(thisApi.FormDomId); };
6988    Object.defineProperty(thisApi, 'FormElement', {
6989        get: function () {
6990            return _getFormElement();
6991        }
6992    });
6993
6994    const _getHeaderMessagesContainer = function () { return document.getElementById(thisApi.HeaderMessagesContainerDomId); };
6995    Object.defineProperty(thisApi, 'HeaderMessagesContainerElement', {
6996        get: function () {
6997            return _getHeaderMessagesContainer();
6998        }
6999    });
7000     
7001    const _getTitleContainerDomIdElement = function () { return document.getElementById(thisApi.TitleContainerDomId); };
7002    Object.defineProperty(thisApi, 'TitleContainerElement', {
7003        get: function () {
7004            return _getTitleContainerDomIdElement();
7005        }
7006    });
7007
7008    const _getHeaderContainerDomIdElement = function () { return document.getElementById(thisApi.HeaderContainerDomId); };
7009    Object.defineProperty(thisApi, 'HeaderContainerElement', {
7010        get: function () {
7011            return _getHeaderContainerDomIdElement();
7012        }
7013    });
7014
7015    const _getFooterContainerDomIdElement = function () { return document.getElementById(thisApi.FooterContentDomId); };
7016    Object.defineProperty(thisApi, 'FooterContainerElement', {
7017        get: function () {
7018            return _getFooterContainerDomIdElement();
7019        }
7020    });
7021
7022    const _getButtonsContainerElement = function () { return document.getElementById(thisApi.ButtonsContainerDomId); };
7023    Object.defineProperty(thisApi, 'ButtonsContainerElement', {
7024        get: function () {
7025            return _getButtonsContainerElement();
7026        }
7027    });
7028
7029    const _getSubmitButtonElement = function () { return document.getElementById(thisApi.SubmitButtonDomId); };
7030    Object.defineProperty(thisApi, 'SubmitButtonElement', {
7031        get: function () {
7032            return _getSubmitButtonElement();
7033        }
7034    });
7035
7036    //const _getBackButtonElement = function () { return document.getElementById(thisApi.BackButtonDomId); };
7037    //Object.defineProperty(thisApi, 'BackButtonElement', {
7038    //    get: function () {
7039    //        return _getBackButtonElement();
7040    //    }
7041    //});    
7042
7043    thisApi.ShowButtons = function () { thisApi.ButtonsContainerElement.show(); };
7044    thisApi.HideButtons = function () { thisApi.ButtonsContainerElement.vanish(); };
7045
7046    thisApi.ShowSubmitButton = function () { thisApi.SubmitButtonElement.show(); };
7047    thisApi.HideSubmitButton = function () { thisApi.SubmitButtonElement.vanish(); };
7048
7049    thisApi.SetSubmitButtonText = function (text) { thisApi.SubmitButtonElement.innerHTML = text; };
7050
7051    //thisApi.ShowBackButton = function () { thisApi.BackButtonElement.show(); };
7052    //thisApi.HideBackButton = function () { thisApi.BackButtonElement.vanish(); };
7053
7054    const _generateButton = function(domId, text, onClickJavascript) {
7055
7056        let btn = document.createElement('button');
7057        btn.setAttribute('type', 'button');
7058        btn.id = domId;
7059        btn.innerHTML = text;
7060        btn.addEventListener('click', function () { onClickJavascript() });
7061
7062        return btn;
7063
7064    };
7065     
7066    thisApi.AddButton = function (domId, text, onClickJavascript) {
7067
7068        let btn = _generateButton(domId, text, onClickJavascript);
7069
7070        thisApi.ButtonsContainerElement.appendChild(btn);
7071
7072    };
7073
7074    thisApi.AddButtonRight = function (domId, text, onClickJavascript) { thisApi.AddButton(domId, text, onClickJavascript); };
7075
7076    thisApi.AddButtonLeft = function (domId, text, onClickJavascript) {
7077
7078        let btn = _generateButton(domId, text, onClickJavascript);
7079
7080        thisApi.ButtonsContainerElement.insertFirstElement(btn);
7081
7082    };
7083    
7084    thisApi.ShowButtonByDomId = function (domId) { if (document.getElementById(domId) != null) document.getElementById(domId).show(); };
7085    thisApi.HideButtonByDomId = function (domId) { if (document.getElementById(domId) != null) document.getElementById(domId).vanish(); };
7086
7087    const _tryGetFormContextIdOrZero = function (configVal) { let retVal = 0; if (configVal != null) retVal = configVal; return retVal; };
7088
7089    //CORE Section & Field APIs (interactions API)
7090    //********************************************
7091     
7092    const _getInitializedFieldApi = function (fieldLookupFunction, defaultGetReturnValueFunction, setValueCallbackFunction, fieldValidationFailedCallbackFunction) {
7093
7094        let retVal = null;
7095
7096        if (typeof fieldLookupFunction == 'function') {
7097
7098            for (let x = 0; x < _allFieldApis.length; x++)
7099                if (fieldLookupFunction(_allFieldApis[x].Configuration)) {
7100                    retVal = _allFieldApis[x];
7101                    break;
7102                }
7103
7104            if (retVal != null) {
7105
7106                retVal.InitializeDefaultGetReturnValueFunction(defaultGetReturnValueFunction);
7107                retVal.InitializeSetValueCallBackFunction(setValueCallbackFunction);
7108                retVal.InitializeFieldValidationFailedCallBackFunction(fieldValidationFailedCallbackFunction);
7109
7110            }
7111            //this means the field isn't configured on the form - not an issue
7112            //else
7113            //    _logger.Log('unable to get initialized form field api - check lookup function (CRITICAL ERROR)');
7114
7115        }
7116        else
7117            _logger.Log('get initialized form field api requires lookup function (CRITICAL ERROR)');
7118
7119        return retVal;
7120
7121    };
7122
7123    const _getFormSectionApi = function (positionNumber, configuration) {
7124
7125        let secApi = {};
7126
7127        secApi.PositionNumber = positionNumber;
7128
7129        secApi.Configuration = configuration;
7130
7131        let _getDomElement = function () { return document.getElementById(secApi.Configuration.DomId); };
7132
7133        Object.defineProperty(secApi, 'DomElement', {
7134            get: function () {
7135                return _getDomElement();
7136            }
7137        });
7138
7139        const _getFormFields = function () {
7140
7141            let retVal = [];
7142
7143            for (let x = 0; x < _allFieldApis.length; x++)
7144                if (_allFieldApis[x].Configuration.Section.Id == secApi.Configuration.Id)
7145                    retVal.push(_allFieldApis[x]);
7146
7147            return retVal;
7148
7149        };
7150
7151        Object.defineProperty(secApi, 'Fields', {
7152            get: function () {
7153                return _getFormFields();
7154            }
7155        });
7156
7157        secApi.Hide = function () {
7158
7159            if (secApi.DomElement != null) {
7160                secApi.DomElement.vanish();
7161                secApi.DomElement.addClassName('ce-validationDisabled');
7162            }
7163
7164        };
7165
7166        secApi.Show = function () {
7167
7168            if (secApi.DomElement != null) {
7169                secApi.DomElement.show();
7170                secApi.DomElement.removeClassName('ce-validationDisabled');
7171            }
7172
7173        };
7174
7175        secApi.Validate = function () {
7176
7177            let valFailCount = 0;
7178
7179            _logger.Log('validate ' + secApi.Fields.length + ' fields');
7180
7181            for (let x = 0; x < secApi.Fields.length; x++) {
7182
7183                let validated = secApi.Fields[x].Validate();
7184                if (!validated)
7185                    valFailCount++;
7186
7187            }
7188
7189            return (valFailCount == 0);
7190
7191        };
7192
7193        return secApi;
7194
7195    };
7196
7197    //Core Field APIS (baseline critical dependency setup)
7198    //****************************************************
7199     
7200    /*App Core Web Form Field Api*/
7201
7202    const _getFormFieldApi = function (configuration) {
7203
7204        const ffApi = {};
7205
7206        ffApi.Configuration = configuration;
7207
7208        ffApi.DefaultGetReturnValueFunction = null;
7209
7210        ffApi.InitializeDefaultGetReturnValueFunction = function (defaultGetReturnValueFunction) {
7211
7212            if (typeof defaultGetReturnValueFunction != 'undefined' && ffApi.DefaultGetReturnValueFunction == null)
7213                ffApi.DefaultGetReturnValueFunction = defaultGetReturnValueFunction;
7214
7215        };
7216
7217        ffApi.SetValueCallbackFunction = null;
7218
7219        ffApi.InitializeSetValueCallBackFunction = function (setValueCallbackFunction) {
7220
7221            if (typeof setValueCallbackFunction != 'undefined' && ffApi.SetValueCallbackFunction == null)
7222                ffApi.SetValueCallbackFunction = setValueCallbackFunction;
7223
7224        };
7225
7226        ffApi.FieldValidationFailedCallbackFunction = null;
7227
7228        ffApi.InitializeFieldValidationFailedCallBackFunction = function (fieldValidationFailedCallbackFunction) {
7229
7230            if (typeof fieldValidationFailedCallbackFunction != 'undefined' && ffApi.FieldValidationFailedCallbackFunction == null)
7231                ffApi.FieldValidationFailedCallbackFunction = fieldValidationFailedCallbackFunction;
7232
7233        };
7234
7235        const _configuredDefaultValue = function () {
7236
7237            let retVal = '';
7238
7239            if (ffApi.Configuration != null && !isBlank(ffApi.Configuration.DefaultValue))
7240                retVal = ffApi.Configuration.DefaultValue.toIntZeroIfInvalid();
7241
7242            return retVal;
7243
7244        };
7245
7246        const _isPaymentMethodBillingInfoField = function () {
7247            return (ffApi.Configuration.DataMapping.BillingAddressStreet1 || ffApi.Configuration.DataMapping.BillingAddressStreet2 || ffApi.Configuration.DataMapping.BillingAddressCity || ffApi.Configuration.DataMapping.BillingAddressState || ffApi.Configuration.DataMapping.BillingAddressPostalCode || ffApi.Configuration.DataMapping.BillingAddressCountry);
7248        };
7249        Object.defineProperty(ffApi, 'IsPaymentMethodBillingInformation', {
7250            get: function () {
7251                return _isPaymentMethodBillingInfoField();
7252            }
7253        });
7254
7255        const _isTributeRecipientAddressField = function () {
7256            return (ffApi.Configuration.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_ADDRESS_1_
7256SYSTEM || ffApi.Configuration.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_ADDRESS_2_SYSTEM || ffApi.Configuration.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_CITY_SYSTEM || ffApi.Configuration.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_STATE_SYSTEM || ffApi.Configuration.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_POSTAL_CODE_SYSTEM || ffApi.Configuration.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_COUNTRY_SYSTEM);
7257        };
7258        Object.defineProperty(ffApi, 'IsTributeRecipientAddressInformation', {
7259            get: function () {
7260                return _isTributeRecipientAddressField();
7261            }
7262        });
7263
7264        const _isTributeRecipientContactInfoField = function () {
7265            return (_isTributeRecipientAddressField() || ffApi.Configuration.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_PHONE_NUMBER_SYSTEM || ffApi.Configuration.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_EMAIL_SYSTEM);
7266        };
7267        Object.defineProperty(ffApi, 'IsTributeRecipientContactInformation', {
7268            get: function () {
7269                return _isTributeRecipientContactInfoField();
7270            }
7271        });
7272
7273        const _isTributeCardConfigurationField = function () {
7274            return (ffApi.Configuration.Type.Id == FORM_FIELD_TYPES.ECARD_SYSTEM || ffApi.Configuration.Type.Id == FORM_FIELD_TYPES.PRINT_CARD_SYSTEM || ffApi.Configuration.Type.Id == FORM_FIELD_TYPES.PRINT_CARD_COUNT_SYSTEM);
7275        };
7276        Object.defineProperty(ffApi, 'IsTributeCardConfiguration', {
7277            get: function () {
7278                return _isTributeCardConfigurationField();
7279            }
7280        });
7281
7282        const _isContactAddressField = function () {
7283            return (ffApi.Configuration.DataMapping.DonorAddressStreet1 || ffApi.Configuration.DataMapping.DonorAddressStreet2 || ffApi.Configuration.DataMapping.DonorAddressCity || ffApi.Configuration.DataMapping.DonorAddressState || ffApi.Configuration.DataMapping.DonorAddressPostalCode || ffApi.Configuration.DataMapping.DonorAddressCountry);
7284        };
7285        Object.defineProperty(ffApi, 'IsContactAddressField', {
7286            get: function () {
7287                return _isContactAddressField();
7288            }
7289        });
7290
7291        const _isEnabled = function () {return ffApi.Configuration.ShowOnLandingPage;};
7292        Object.defineProperty(ffApi, 'Enabled', {
7293            get: function () {
7294                return _isEnabled();
7295            }
7296        });
7297
7298        Object.defineProperty(ffApi, 'DefaultValue', {
7299            get: function () {
7300                return _configuredDefaultValue();
7301            }
7302        });
7303
7304        Object.defineProperty(ffApi, 'DefaultIntegerValue', {
7305            get: function () {
7306                return _configuredDefaultValue().toIntZeroIfInvalid();
7307            }
7308        });
7309
7310        Object.defineProperty(ffApi, 'DefaultDecimalValue', {
7311            get: function () {
7312                return _configuredDefaultValue().toDecimalZeroIfInvalid();
7313            }
7314        });
7315
7316        const _tryGetFieldDomElement = function (field) {
7317
7318            let retVal = null;
7319
7320            if (field != null)
7321                retVal = document.getElementById(field.DomId);
7322
7323            return retVal;
7324
7325        }
7326
7327        const _getDomElement = function () {return _tryGetFieldDomElement(ffApi.Configuration);};
7328        Object.defineProperty(ffApi, 'DomElement', {
7329            get: function () {
7330                return _getDomElement();
7331            }
7332        });
7333
7334        const _tryGetFieldElementValue = function (fieldElem) {
7335
7336            let retVal = '';
7337
7338            if (fieldElem != null) {
7339
7340                try {
7341                    retVal = fieldElem.getValue();
7342                }
7343                catch {
7344                    _logger.Log('failed to get value for field ' + ffApi.Configuration.DomId + ' (incompatible value field)');
7345                }
7346
7347            }
7348
7349            return retVal;
7350
7351        }
7352
7353        const _getUnAlteredFieldValue = function () {return _tryGetFieldElementValue(ffApi.DomElement);};
7354
7355        const _getValue = function () {
7356
7357            let retVal = _getUnAlteredFieldValue();
7358
7359            if (typeof ffApi.DefaultGetReturnValueFunction == 'function')
7360                retVal = ffApi.DefaultGetReturnValueFunction(retVal);
7361
7362            return retVal;
7363
7364        };
7365        Object.defineProperty(ffApi, 'Value', {
7366            get: function () {
7367                return _getValue();
7368            }
7369        });
7370
7371        Object.defineProperty(ffApi, 'HasValue', {
7372            get: function () {
7373                return isBlank(ffApi.Value);
7374            }
7375        });
7376
7377        let _fileNameValue = null;
7378
7379        ffApi.SetFileNameValue = function (val) {_fileNameValue = val;};
7380        ffApi.GetFileNameValue = function () {return _fileNameValue;};
7381
7382        let _fileBase64Value = null;
7383
7384        ffApi.SetBase64Value = function (val) {_fileBase64Value = val;};
7385        ffApi.GetBase64Value = function () {return _fileBase64Value;};
7386
7387        const _tryGetFieldElementText = function (fieldElem) {
7388
7389            let retVal = '';
7390
7391            if (fieldElem != null)
7392                retVal = fieldElem.getText();
7393
7394            return retVal;
7395
7396        }
7397
7398        const _getText = function () {return _tryGetFieldElementText(ffApi.DomElement);};
7399        Object.defineProperty(ffApi, 'Text', {
7400            get: function () {
7401                return _getText();
7402            }
7403        });
7404
7405        const _trySetFieldElementValue = function (fieldElem, val) {
7406
7407            if (fieldElem != null) {
7408
7409                if (fieldElem.hasClassName('ce-displayFields'))
7410                    fieldElem.innerHTML = val;
7411                else
7412                    fieldElem.setValue(val);
7413
7414            }
7415
7416        }
7417
7418        ffApi.SetValue = function (val) {
7419
7420            _trySetFieldElementValue(ffApi.DomElement, val);
7421
7422            if (typeof ffApi.SetValueCallbackFunction == 'function')
7423                ffApi.SetValueCallbackFunction();
7424
7425        };
7426
7427        const _getSection = function () {
7428
7429            let retVal = null;
7430
7431            for (let x = 0;x < _allSectionApis.length;x++)
7432                if (_allSectionApis[x].Configuration.Id == ffApi.Configuration.Section.Id) {
7433                    retVal = _allSectionApis[x];
7434                    break;
7435                }
7436
7437            return retVal;
7438
7439        };
7440        Object.defineProperty(ffApi, 'Section', {
7441            get: function () {
7442                return _getSection();
7443            }
7444        });
7445
7446        const _getSectionConfiguration = function () {
7447
7448            let retVal = null;
7449
7450            if (ffApi.Section != null)
7451                retVal = ffApi.Section.Configuration;
7452            //for (let x = 0; x < WebFormContext.Form.Sections.length; x++)
7453            //    if (WebFormContext.Form.Sections[x].Id == ffApi.Configuration.Section.Id) {
7454            //        retVal = WebFormContext.Form.Sections[x];
7455            //        break;
7456            //    }
7457
7458            return retVal;
7459
7460        };
7461        Object.defineProperty(ffApi, 'SectionConfiguration', {
7462            get: function () {
7463                return _getSectionConfiguration();
7464            }
7465        });
7466
7467        const _getSectionDomElement = function () {
7468
7469            let retVal = null;
7470
7471            if (ffApi.DomElement != null)
7472                retVal = ffApi.DomElement.firstParentWithClass('ce-formSections');
7473
7474            return retVal;
7475
7476        };
7477        Object.defineProperty(ffApi, 'SectionDomElement', {
7478            get: function () {
7479                return _getSectionDomElement();
7480            }
7481        });
7482
7483        const _getLabelDomElement = function () {
7484
7485            let retVal = null;
7486
7487            if (ffApi.DomElement != null)
7488                retVal = getLabelForField(ffApi.DomElement);
7489
7490            return retVal;
7491
7492        };
7493        Object.defineProperty(ffApi, 'LabelDomElement', {
7494            get: function () {
7495                return _getLabelDomElement();
7496            }
7497        });
7498
7499        const _getLabelText = function () {
7500
7501            let retVal = null;
7502
7503            if (ffApi.LabelDomElement != null)
7504                retVal = ffApi.LabelDomElement.innerText;
7505
7506            return retVal;
7507
7508        };
7509        Object.defineProperty(ffApi, 'LabelText', {
7510            get: function () {
7511                return _getLabelText();
7512            }
7513        });
7514
7515        const _getPlaceholderText = function () {
7516
7517            let retVal = null;
7518
7519            if (ffApi.DomElement != null && ffApi.DomElement.placeholder)
7520                retVal = ffApi.DomElement.placeholder;
7521
7522
7523            return retVal;
7524
7525        };
7526        Object.defineProperty(ffApi, 'PlaceholderText', {
7527            get: function () {
7528                return _getPlaceholderText();
7529            }
7530        });
7531
7532        const _getWrapperDomElement = function () {
7533
7534            let retVal = null;
7535
7536            if (ffApi.DomElement != null)
7537                retVal = ffApi.DomElement.firstParentWithClass('ce-formFieldWrappers');
7538
7539            return retVal;
7540
7541        };
7542        Object.defineProperty(ffApi, 'WrapperDomElement', {
7543            get: function () {
7544                return _getWrapperDomElement();
7545            }
7546        });
7547
7548        const _getRowDomElement = function () {
7549
7550            let retVal = null;
7551
7552            if (ffApi.DomElement != null)
7553                retVal = ffApi.DomElement.firstParentWithClass('ce-formRows');
7554
7555            return retVal;
7556
7557        };
7558        Object.defineProperty(ffApi, 'RowDomElement', {
7559            get: function () {
7560                return _getRowDomElement();
7561            }
7562        });
7563
7564        //const _isHidden = function () {
7565        //
7566        //    let retVal = false;
7567        //
7568        //    if (ffApi.DomElement != null && ffApi.DomElement.firstParentWithClass('ce-formFieldContainers') != null && ffApi.DomElement.firstParentWithClass('ce-formFieldContainers').hasClassName('ce-validationDisabled'))
7569        //        retVal = true;
7570        //
7571        //    return retVal;                
7572        //
7573        //};
7574        //Object.defineProperty(ffApi, 'Hidden', {
7575        //    get: function () {
7576        //        return _isHidden();
7577        //    }
7578        //});
7579        //
7580        //const _isVisible = function () { return !_isHidden(); };
7581        //Object.defineProperty(ffApi, 'Visible', {
7582        //    get: function () {
7583        //        return _isVisible();
7584        //    }
7585        //});
7586
7587        const _rowHasVisibleFields = function () {return (ffApi.RowDomElement.querySelectorAll('.ce-formFieldContainers:not([style*="display: none"])').length > 0);};
7588        Object.defineProperty(ffApi, 'RowHasVisibleFields', {
7589            get: function () {
7590                return _rowHasVisibleFields();
7591            }
7592        });
7593
7594        const _hideRow = function (disableValidation = true) {
7595            ffApi.RowDomElement.vanish();
7596            if (disableValidation)
7597                ffApi.RowDomElement.addClassName('ce-validationDisabled');
7598        };
7599
7600        const _showRow = function () {
7601            ffApi.RowDomElement.show();
7602            ffApi.RowDomElement.removeClassName('ce-validationDisabled');
7603        };
7604
7605        ffApi.Hide = function (disableValidation = true) {
7606
7607            if (ffApi.DomElement != null && ffApi.DomElement.firstParentWithClass('ce-formFieldContainers') != null) {
7608
7609                ffApi.DomElement.firstParentWithClass('ce-formFieldContainers').vanish();
7610                if (disableValidation)
7611                    ffApi.DomElement.firstParentWithClass('ce-formFieldContainers').addClassName('ce-validationDisabled');
7612
7613                if (!ffApi.RowHasVisibleFields)
7614                    _hideRow(disableValidation);
7615
7616            }
7617
7618        };
7619
7620        ffApi.Show = function () {
7621
7622            //NOTE: never show hidden form fields!!!
7623            if (ffApi.DomElement != null && ffApi.DomElement.firstParentWithClass('ce-formFieldContainers') != null && !ffApi.DomElement.hasParentWithClass('ce-emptyRows')) {
7624
7625                ffApi.DomElement.firstParentWithClass('ce-formFieldContainers').show();
7626                ffApi.DomElement.firstParentWithClass('ce-formFieldContainers').removeClassName('ce-validationDisabled');
7627
7628                _showRow();
7629
7630            }
7631
7632        };
7633
7634        ffApi.HideLabel = function () {
7635
7636            let lbl = ffApi.LabelDomElement;
7637            if (lbl != null)
7638                lbl.vanish();
7639
7640        };
7641
7642        ffApi.ShowLabel = function () {
7643
7644            let lbl = ffApi.LabelDomElement;
7645            if (lbl != null)
7646                lbl.show();
7647
7648        };
7649
7650        ffApi.HideOption = function (fieldElemOpt) {
7651
7652            if (fieldElemOpt != null && fieldElemOpt.firstParentWithClass('ce-formFieldOptionItems') != null)
7653                fieldElemOpt.firstParentWithClass('ce-formFieldOptionItems').vanish();
7654
7655        };
7656
7657        ffApi.ShowOption = function (fieldElemOpt) {
7658
7659            if (fieldElemOpt != null && fieldElemOpt.firstParentWithClass('ce-formFieldOptionItems') != null)
7660                fieldElemOpt.firstParentWithClass('ce-formFieldOptionItems').show();
7661
7662        };
7663
7664        const _getOptions = function () {
7665
7666            let retVal = [];
7667
7668            if (ffApi.DomElement != null)
7669                retVal = getMultiSelectInputOptionElements(ffApi.DomElement);
7670
7671            return retVal;
7672
7673        };
7674
7675        Object.defineProperty(ffApi, 'Options', {
7676            get: function () {
7677                return _getOptions();
7678            }
7679        });
7680
7681        Object.defineProperty(ffApi, 'OptionCount', {
7682            get: function () {
7683                return ffApi.Options.length;
7684            }
7685        });
7686
7687        Object.defineProperty(ffApi, 'HasOptions', {
7688            get: function () {
7689                return (ffApi.OptionCount > 0);
7690            }
7691        });
7692
7693        const _getSelectedOptions = function () {
7694
7695            let retVal = [];
7696
7697            for (let x = 0;x < ffApi.Options.length;x++)
7698                if (ffApi.Options[x].checked)
7699                    retVal.push(ffApi.Options[x]);
7700
7701            return retVal;
7702
7703        };
7704
7705        Object.defineProperty(ffApi, 'SelectedOptions', {
7706            get: function () {
7707                return _getSelectedOptions();
7708            }
7709        });
7710
7711        const _getSelectedOptionValues = function () {
7712
7713            let retVal = [];
7714
7715            for (let x = 0;x < ffApi.SelectedOptions.length;x++)
7716                retVal.push(ffApi.SelectedOptions[x].getValue(true)); //we don't want true/falst here (ever!)
7717
7718            return retVal;
7719
7720        };
7721
7722        Object.defineProperty(ffApi, 'SelectedOptionValues', {
7723            get: function () {
7724                return _getSelectedOptionValues();
7725            }
7726        });
7727
7728        const _getSelectedOptionIdValues = function () {
7729
7730            let retVal = [];
7731
7732            for (let x = 0;x < ffApi.SelectedOptionValues.length;x++) {
7733                _logger.Log('_getSelectedOptionIdValues ' + x + ': ' + ffApi.SelectedOptionValues[x]);
7734                retVal.push(ffApi.SelectedOptionValues[x].toString().toIntZeroIfInvalid());
7735            }
7736
7737            return retVal;
7738
7739        };
7740
7741        Object.defineProperty(ffApi, 'SelectedOptionIdValues', {
7742            get: function () {
7743                return _getSelectedOptionIdValues();
7744            }
7745        });
7746
7747        const _getOptionValues = function () {
7748
7749            let retVal = [];
7750
7751            if (ffApi.OptionCount > 0)
7752                for (let x = 0;x < ffApi.OptionCount;x++)
7753                    retVal.push(ffApi.Options[x].value);
7754
7755            return retVal;
7756
7757        };
7758
7759        Object.defineProperty(ffApi, 'OptionValues', {
7760            get: function () {
7761                return _getOptionValues();
7762            }
7763        });
7764
7765        const _getOptionIdValues = function () {
7766
7767            let retVal = [];
7768
7769            for (let x = 0;x < ffApi.OptionValues.length;x++)
7770                retVal.push(ffApi.OptionValues[x].toIntZeroIfInvalid());
7771
7772            return retVal;
7773
7774        };
7775
7776        Object.defineProperty(ffApi, 'OptionIdValues', {
7777            get: function () {
7778                return _getOptionIdValues();
7779            }
7780        });
7781
7782        const _getFirstOption = function () {
7783
7784            let retVal = null;
7785
7786            let opts = _getOptions();
7787            if (opts.length > 0)
7788                retVal = opts[0];
7789
7790            return retVal;
7791
7792        };
7793
7794        Object.defineProperty(ffApi, 'FirstOption', {
7795            get: function () {
7796                return _getFirstOption();
7797            }
7798        });
7799
7800        ffApi.SelectFirstOption = function () {
7801
7802            if (ffApi.FirstOption != null)
7803                ffApi.SetValue(ffApi.FirstOption.value.toIntZeroIfInvalid());
7804            //else
7805            //    _logger.Log('unable to set first option value (element not found on form)');
7806
7807        };
7808
7809        ffApi.GetOptionByValue = function (val) {
7810
7811            let retVal = null;
7812
7813            if (ffApi.DomElement != null)
7814                retVal = getMultiSelectInputOptionElementByValue(ffApi.DomElement, val);
7815
7816            return retVal;
7817
7818        };
7819
7820        ffApi.HideOptionByValue = function (val) {
7821
7822            let opt = ffApi.GetOptionByValue(val);
7823            if (opt != null)
7824                ffApi.HideOption(opt)
7825
7826        };
7827
7828        const _getSelectedOptionInput = function () {
7829
7830            let retVal = null;
7831
7832            if (ffApi.DomElement != null)
7833                retVal = getMultiSelectInputSelectedOptionElement(ffApi.DomElement);
7834
7835            return retVal;
7836
7837        };
7838
7839        Object.defineProperty(ffApi, 'SelectedOptionInput', {
7840            get: function () {
7841                return _getSelectedOptionInput();
7842            }
7843        });
7844
7845        Object.defineProperty(ffApi, 'SelectedOptionDomElement', {
7846            get: function () {
7847
7848                let retVal = null;
7849
7850                let optInput = ffApi.SelectedOptionInput;
7851                if (optInput != null)
7852                    retVal = optInput.firstParentOrSelfWithClass('ce-formFieldOptionItems')
7853
7854                return retVal;
7855
7856            }
7857        });
7858
7859        const _validateRequiredField = function (field) {
7860
7861            let retVal = true;
7862
7863            //_logger.Log('domId: ' + field.DomId);
7864            //_logger.Log('field.DomElement: ' + fieldElem.outerHTML);
7865            field.DomElement.validationSucceeded();
7866
7867            switch (field.Configuration.Type.Id) {
7868                case FORM_FIELD_TYPES.RADIO_BUTTON:
7869                case FORM_FIELD_TYPES.CHECK_BOX:
7870
7871                    let anySelected = false;
7872
7873                    let inputs = field.DomElement.querySelectorAll('input');
7874                    for (let x = 0;x < inputs.length;x++) {
7875
7876                        inputs[x].validationSucceeded();
7877
7878                        if (inputs[x].checked) {
7879                            anySelected = true;
7880                            break;
7881                        }
7882
7883                    }
7884
7885                    if (!anySelected) {
7886                        retVal = false;
7887                        field.DomElement.validationFailed(); //base container
7888                        for (let x = 0;x < inputs.length;x++) inputs[x].validationFailed(); //individual inputs (not sure if necessary)
7889                    }
7890
7891                    break;
7892                default:
7893
7894                    if (isBlank(field.DomElement.getValue())) {
7895                        retVal = false;
7896                        field.DomElement.validationFailed();
7897                    }
7898
7899                    break;
7900            }
7901
7902            return retVal;
7903
7904        };
7905
7906        const _validateEmailField = function (field) {
7907
7908            let retVal = true;
7909
7910            //_logger.Log('domId: ' + field.DomId);
7911            //_logger.Log('field.DomElement: ' + fieldElem.outerHTML);
7912            field.DomElement.validationSucceeded();
7913
7914            let fldVal = field.DomElement.getValue();
7915            if (!isBlank(fldVal) && !isValidEmailAddress(fldVal)) {
7916                retVal = false;
7917                field.DomElement.validationFailed(); //base container
7918            }
7919
7920            return retVal;
7921
7922        };
7923
7924        const _validateCreditCardExpirationDateField = function (field) {
7925
7926            let retVal = true;
7927
7928            //_logger.Log('domId: ' + field.DomId);
7929            //_logger.Log('field.DomElement: ' + fieldElem.outerHTML);
7930            field.DomElement.validationSucceeded();
7931
7932            let fldVal = field.DomElement.getValue();
7933            //always required            
7934            if (isBlank(fldVal) || !isExpirationDateValidMMYYFormat(fldVal) || !isMMYYExpirationDateValid(fldVal)) {
7935                retVal = false;
7936                field.DomElement.validationFailed(); //base container
7937            }
7938
7939            return retVal;
7940
7941        };
7942
7943        const _validateAmountField = function (field) {
7944
7945            let retVal = true;
7946
7947            field.DomElement.validationSucceeded();
7948
7949            const fldVal = field.DomElement.getValue();
7950
7951            if (!isBlank(fldVal) && !/^(\d+\.?\d{0,2}|\.\d{1,2})$/.test(fldVal.toString().trim())) {
7952                retVal = false;
7953                field.DomElement.validationFailed();
7954            }
7955
7956            return retVal;
7957
7958        };
7959
7960        ffApi.Validate = function () {
7961
7962            let retVal = true;
7963
7964            if (ffApi.DomElement != null) {
7965
7966                if (!ffApi.DomElement.hasParentWithClass('ce-validationDisabled')) {
7967
7968                    if (retVal && ffApi.Configuration.Validation.Required)
7969                        retVal = _validateRequiredField(ffApi);
7970
7971                    if (retVal && ffApi.Configuration.Validation.Email)
7972                        retVal = _validateEmailField(ffApi);
7973
7974                    if (retVal && ffApi.Configuration.Type.Id == FORM_FIELD_TYPES.CREDIT_CARD_EXPIRATION_DATE)
7975                        retVal = _validateCreditCardExpirationDateField(ffApi);
7976
7977                    if (retVal && _isAnyAmountInputField(ffApi.Configuration))
7978                        retVal = _validateAmountField(ffApi);
7979
7980                    if (!retVal) {
7981                        if (typeof ffApi.FieldValidationFailedCallbackFunction == 'function')
7982                            ffApi.FieldValidationFailedCallbackFunction();
7983
7984                        _logger.Log('validation failed for field ' + ffApi.Configuration.Id + ' (' + ffApi.Configuration.Name + ')');
7985                    }
7986
7987                }
7988                else
7989                    _logger.Log('validation disabled for field ' + ffApi.Configuration.Id + ' (' + ffApi.Configuration.Name + ')');
7990
7991            }
7992
7993            //not sure this is needed for drop downs (ffApi.Configuration.Validation.Currency)
7994
7995            return retVal;
7996
7997        };
7998
7999        return ffApi;
8000
8001};
8002
8003    const _allSectionApis = [];
8004
8005    for (let x = 0; x < WebFormContext.Form.Sections.length; x++) {
8006
8007        let newApi = _getFormSectionApi(x, WebFormContext.Form.Sections[x]);
8008
8009        _allSectionApis.push(newApi);
8010
8011    }
8012
8013    const _allFieldApis = [];
8014
8015    for (let x = 0; x < WebFormContext.Form.Fields.length; x++) {
8016
8017        let newApi = _getFormFieldApi(WebFormContext.Form.Fields[x]);
8018
8019        _allFieldApis.push(newApi);
8020
8021    }
8022
8023    //Core UI & Framework
8024    //******************************************
8025    //******************************************
8026
8027    //CORE WebFormContext Helper APIs (ROWS)
8028    //**************************************
8029
8030    const _createFormLabel = function (domId, text) {
8031
8032        let retVal = document.createElement('label');
8033
8034        if (!isBlank(domId)) { 
8035            retVal.setAttribute('for', domId);
8036            retVal.innerText = text;
8037        }
8038
8039        return retVal;
8040
8041    };
8042
8043    const _createFormRow = function (domId, classes) {
8044
8045        let classVal = 'ce-formRows';
8046
8047        if (!isBlank(classes))
8048            classVal += ' ' + classes;
8049
8050        return createDomElement('div', domId, classVal);
8051
8052    };
8053
8054    const _createFormFieldContainer = function (domId, classes) {
8055
8056        let classVal = 'ce-formFieldContainers';
8057
8058        if (!isBlank(classes))
8059            classVal += ' ' + classes;
8060
8061        return createDomElement('div', domId, classVal);
8062
8063    };
8064     
8065    const _createFormFieldWrapper = function (domId, classes) {
8066
8067        let classVal = 'ce-formFieldWrappers';
8068
8069        if (!isBlank(classes))
8070            classVal += ' ' + classes;
8071
8072        return createDomElement('div', domId, classVal);
8073
8074    };
8075
8076    const _createRowWithSingleDisplayElement = function (domId, classes, displayElementDomId, label, displayElementClasses) {
8077
8078        let row = _createFormRow(domId, classes);
8079
8080        let fldCont = _createFormFieldContainer(domId + '_container', '');
8081
8082        if (!isBlank(label)) 
8083            fldCont.appendChild(_createFormLabel(displayElementDomId,label)); 
8084
8085        let displayElem = document.createElement('span');
8086        displayElem.id = displayElementDomId;
8087        displayElem.addClassNames(displayElementClasses);
8088
8089        fldCont.appendChild(displayElem);
8090
8091        row.appendChild(fldCont);
8092
8093        return row;
8094
8095    };
8096
8097    const _addRowWithSingleDisplayElement = function (sectionElement, domId, classes, displayElementDomId, label, displayElementClasses) {
8098
8099        if (sectionElement != null) {
8100
8101            let row = _createRowWithSingleDisplayElement(domId, classes, displayElementDomId, label, displayElementClasses);
8102
8103            sectionElement.appendChild(row);
8104
8105        }
8106
8107    };
8108
8109    const _createRowWithSingleTextInput = function (domId, classes, inputDomId, label, inputClasses, placeHolder) {
8110
8111        let row = _createFormRow(domId, classes);
8112
8113        let fldCont = _createFormFieldContainer(domId + '_container', '');
8114
8115        if (!isBlank(label)) 
8116            fldCont.appendChild(_createFormLabel(inputDomId,label)); 
8117
8118        fldCont.addTextBox(inputDomId, inputClasses, null, placeHolder);
8119
8120        row.appendChild(fldCont);
8121
8122        return row;
8123
8124    };
8125
8126    const _addRowWithSingleTextInput = function (sectionElement, domId, classes, inputDomId, label, inputClasses, placeHolder) {
8127
8128        if (sectionElement != null) {
8129
8130            let row = _createRowWithSingleTextInput(domId, classes, inputDomId, label, inputClasses, placeHolder);
8131
8132            sectionElement.appendChild(row);
8133
8134        }
8135
8136    };
8137
8138    const _createRowWithSingleCheckBoxOption = function (domId, classes, checkBoxDomId, checkBoxValue, checkBoxText, checkBoxSelected, clickJavascript) {
8139
8140        let row = _createFormRow(domId, classes);
8141
8142        let opts = [];
8143        opts.push(createMultiSelectInputOptionConfigJson(checkBoxValue, checkBoxText, checkBoxSelected));
8144
8145        row.addCheckBoxList(checkBoxDomId, clickJavascript, opts);
8146
8147        return row;
8148
8149    };
8150
8151    const _addRowWithSingleCheckBoxOption = function (sectionElement, domId, classes, checkBoxDomId, checkBoxValue, checkBoxText, checkBoxSelected, clickJavascript) {
8152
8153        if (sectionElement != null) {
8154
8155            let row = _createRowWithSingleCheckBoxOption(domId, classes, checkBoxDomId, checkBoxValue, checkBoxText, checkBoxSelected, clickJavascript);
8156
8157            sectionElement.appendChild(row);
8158
8159        }
8160
8161    };
8162     
8163    const _createRowWithRadioButtonList = function (domId, classes, radioButtonListDomId, label, optionsCsv, selectedOptionValue, clickJavascript) {
8164
8165        let row = _createFormRow(domId, classes);
8166
8167        let fldCont = _createFormFieldContainer(domId + '_container', '');
8168
8169        if (!isBlank(label)) 
8170            fldCont.appendChild(_createFormLabel(radioButtonListDomId,label)); 
8171
8172        let fldWarp = _createFormFieldWrapper();
8173
8174        fldWarp.addRadioButtonListFromCsv(radioButtonListDomId, clickJavascript, optionsCsv, selectedOptionValue, null, null, 'ce-formFieldOptionItems');
8175
8176        fldCont.appendChild(fldWarp);
8177
8178        row.appendChild(fldCont);
8179
8180        return row;
8181
8182    };
8183
8184    const _createRowWithText = function (domId, classes, text, contentDomId) {
8185
8186        let row = _createFormRow(domId, classes);
8187
8188        row.addText(text, contentDomId);
8189
8190        return row;
8191
8192    };
8193
8194    const _addRowWithText = function (sectionElement, domId, classes, text, contentDomId) {
8195
8196        if (sectionElement != null) {
8197
8198            let row = _createRowWithText(domId, classes, text, contentDomId);
8199
8200            sectionElement.appendChild(row);
8201
8202        }
8203
8204    };
8205
8206    const _createRowWithHtml = function (domId, classes, html, contentDomId) {
8207
8208        let row = _createFormRow(domId, classes);
8209
8210        row.addHtml(html, contentDomId);
8211
8212        return row;
8213
8214    };
8215
8216    const _addRowWithHtml = function (sectionElement, domId, classes, html, contentDomId) {
8217
8218        if (sectionElement != null) {
8219
8220            let row = _createRowWithHtml(domId, classes, html, contentDomId);
8221
8222            sectionElement.appendChild(row);
8223
8224        }
8225
8226    };
8227
8228    //CORE WebFormContext Helper APIs (GENERIC SECTIONS)
8229    //**************************************************
8230
8231    Object.defineProperty(thisApi, 'Sections', {
8232        get: function () {
8233            return _allSectionApis;
8234        }
8235    });    
8236
8237    const _getSectionById = function (section_id) {
8238
8239        let retVal = null;
8240
8241        for (let x = 0; x < _allSectionApis.length; x++)
8242            if (_allSectionApis[x].Configuration.Id == section_id) {
8243                retVal = _allSectionApis[x];
8244                break;
8245            }
8246
8247        return retVal;
8248
8249    };
8250
8251    const _getSectionByIndex = function (sectionIndexPosition) {
8252
8253        let retVal = null;
8254
8255        for (let x = 0; x < _allSectionApis.length; x++)
8256            if (_allSectionApis[x].PositionNumber == sectionIndexPosition) {
8257                retVal = _allSectionApis[x];
8258                break;
8259            }
8260
8261        return retVal;
8262
8263    };
8264
8265    const _tryHideSectionElement = function (sectionElem) {
8266
8267        if (sectionElem != null) {
8268            sectionElem.vanish();
8269            sectionElem.addClassName('ce-validationDisabled');
8270        }
8271
8272    };
8273
8274    const _tryShowSectionElement = function (sectionElem) {
8275
8276        if (sectionElem != null) {
8277            sectionElem.show();
8278            sectionElem.removeClassName('ce-validationDisabled');
8279        }
8280
8281    };
8282
8283    const _tryHideSection = function (sectionConfig) { _tryHideSectionElement(document.getElementById(sectionConfig.DomId)); };
8284    const _tryShowSection = function (sectionConfig) { _tryShowSectionElement(document.getElementById(sectionConfig.DomId)); };
8285
8286    const _trySetFieldValue = function (field, val) { if (field != null) field.SetValue(val); };
8287    const _tryGetFieldValue = function (field) { let retVal = null; if (field != null) retVal = field.Value; return retVal; };
8288    const _tryGetFieldText = function (field) { let retVal = null; if (field != null) retVal = field.Text; return retVal; };
8289
8290    const _tryGetFieldValueOrZero = function (field) { let retVal = 0; if (field != null) retVal = field.Value; return retVal; };
8291    const _tryGetFieldValueOrFalse = function (field) { let retVal = false; if (field != null) retVal = Boolean(field.Value); return retVal; };
8292    const _tryGetFieldBoolValueOrNull = function (field) { let retVal = null; if (field != null) retVal = Boolean(field.Value); return retVal; };
8293    const _tryGetFieldDateTimeValue = function (field) { let retVal = null; if (field != null && field.Value != null) retVal = field.Value.toString().toDateTimeOrNull(); return retVal; };
8294    const _tryGetFieldDotNetDateTimeValue = function (field) { let retVal = null; if (field != null) retVal = formatDotNetJsonDate(field.Value); return retVal; };
8295
8296    const _tryGetIdFieldValueOrZero = function (field) { let retVal = 0; if (field != null) retVal = field.Value; return retVal; };
8297
8298    const _tryGetDomInputTextValueOrNull = function (domInput) { let retVal = null; if (domInput != null) retVal = domInput.getValue(); return retVal; };
8299    const _tryGetDomInputIntegerValueOrZero = function (domInput) { let retVal = 0; if (domInput != null) retVal = domInput.getValue().toIntZeroIfInvalid(); return retVal; };
8300    const _tryGetDomInputDecimalValueOrZero = function (domInput) { let retVal = 0; if (domInput != null) retVal = domInput.getValue().toDecimalZeroIfInvalid(); return retVal; };
8301
8302    //CORE WebFormContext Helper APIs (SPECIFIC SECTIONS)
8303    //***************************************************
8304
8305    const _getDonationAmountSectionConfig = function () {
8306
8307        let retVal = null;
8308
8309        if (WebFormContext.Form.AmountSectionConfig != null)
8310            retVal = WebFormContext.Form.AmountSectionConfig
8311
8312        return retVal;
8313
8314    };
8315
8316    Object.defineProperty(thisApi, 'DonationAmountSectionConfig', {
8317        get: function () {
8318            return _getDonationAmountSectionConfig();
8319        }
8320    });
8321
8322    Object.defineProperty(thisApi, 'PaymentSection', {
8323        get: function () { return thisApi.PaymentMethodField.Section; }
8324    })
8325
8326    // {"PaymentSectionConfig":{"EnableApplePaySubscriptions":false,"EnablePayPalSubscription":false,"EnablePayPalSubscriptions":false,"IncludeAcH":true,"IncludeCVV":false,"IncludeCreditCard":true,"IncludeDafPay":true,"IncludeNameOnCard":true,"IncludePayPal":true}
8327    const _getPaymentSectionConfig = function () {
8328
8329        let retVal = null;
8330
8331        if (WebFormContext.Form.PaymentSectionConfig != null)
8332            retVal = WebFormContext.Form.PaymentSectionConfig
8333
8334        return retVal;
8335
8336    };
8337
8338    Object.defineProperty(thisApi, 'PaymentSectionConfig', {
8339        get: function () {
8340            return _getPaymentSectionConfig();
8341        }
8342    });
8343
8344    // {"TributeSectionConfig":{"EmailAcknowledgementLabel":"Email Announcement","EnableEmailAcknowledgement":true,"EnableMailAcknowledgement":true,"MailAcknowledgementLabel":"Mail Announcement","NoAcknowledgementLabel":"No Announcement"}
8345    const _getTributeSectionConfig = function () {
8346
8347        let retVal = null;
8348
8349        if (WebFormContext.Form.TributeSectionConfig != null)
8350            retVal = WebFormContext.Form.TributeSectionConfig
8351
8352        return retVal;
8353
8354    };
8355
8356    Object.defineProperty(thisApi, 'TributeSectionConfig', {
8357        get: function () {
8358            return _getTributeSectionConfig();
8359        }
8360    });
8361
8362    //CORE WebFormContext Helper APIs (GENERIC FIELDS)
8363    //************************************************    
8364
8365    Object.defineProperty(thisApi, 'Fields', {
8366        get: function () {
8367            return _allFieldApis;
8368        }
8369    });
8370
8371    const _getFieldById = function (field_id) {
8372
8373        let retVal = null;
8374
8375        for (let x = 0; x < _allFieldApis.length; x++)
8376            if (_allFieldApis[x].Configuration.Id == field_id) {
8377                retVal = _allFieldApis[x];
8378                break;
8379            }
8380
8381        return retVal;
8382
8383    };
8384
8385    const _getFieldByDomId = function (domId) {
8386
8387        let retVal = null;
8388
8389        if (!isBlank(domId)) { 
8390
8391            for (let x = 0; x < _allFieldApis.length; x++)
8392                if (_allFieldApis[x].Configuration.DomId == domId) {
8393                    retVal = _allFieldApis[x];
8394                    break;
8395                }
8396
8397        }
8398
8399        return retVal;
8400
8401    };    
8402
8403    const _getFieldElementFieldId = function (fieldOrChildElement) {
8404
8405        let retVal = 0;
8406
8407        if (fieldOrChildElement != null)
8408            retVal = fieldOrChildElement.tryGetIntegerAttribute('data-field-id');
8409
8410        return retVal;
8411
8412    };
8413
8414    const _tryHideField = function (field) { if (field != null) field.Hide(); };
8415    const _tryShowField = function (field) { if (field != null) field.Show(); };
8416
8417    const _tryShowFieldIfHasOptions = function (field) { if (field != null && field.HasOptions) field.Show(); };
8418
8419    //CORE WebFormContext Helper APIs (SPECIFIC FIELDS)
8420    //*************************************************
8421
8422    //rule: ignore legacy mapped ticket quantity (we're going to derive membership info from type)
8423    const _isGenericAmountInputField = function (fieldConfig) { return (fieldConfig.DataMapping.TransactionAmount && fieldConfig.Type.Id != FORM_FIELD_TYPES.TICKET_QUANTITY_SYSTEM); };
8424
8425    const _getGenericAmountInputFields = function () {
8426
8427        let retVal = [];
8428
8429        for (let x = 0; x < _allFieldApis.length; x++)
8430            if (_isGenericAmountInputField(_allFieldApis[x].Configuration)) {
8431
8432                _allFieldApis[x].InitializeDefaultGetReturnValueFunction(function (rawVal) { return rawVal.toDecimalZeroIfInvalid(); });
8433
8434                //console.log('get generic input amount field API for ' + _allFieldApis[x].Configuration.DomId);
8435                //console.log('call back ' + _allFieldApis[x].SetValueCallbackFunction);
8436                
8437                _allFieldApis[x].InitializeSetValueCallBackFunction(function () { _handlePaymentAmountChanged(); });
8438
8439                retVal.push(_allFieldApis[x]); 
8440
8441            }
8442
8443        return retVal;
8444
8445    };
8446    Object.defineProperty(thisApi, 'GenericAmountInputFields', { get: function () { return _getGenericAmountInputFields(); } });
8447
8448    const _getGenericAmountInputAmounts = function () {
8449
8450        let retVal = [];
8451
8452        for (let x = 0; x < thisApi.GenericAmountInputFields.length; x++)
8453            retVal.push(thisApi.GenericAmountInputFields[x].Value);
8454            
8455        return retVal;
8456
8457    };
8458
8459    Object.defineProperty(thisApi, 'GenericAmountInputAmounts', { get: function () { return _getGenericAmountInputAmounts(); } });
8460
8461    const _getGenericAmountInputAmount = function () {
8462
8463        let retVal = 0.00;
8464
8465        for (let x = 0; x < thisApi.GenericAmountInputAmounts.length; x++)
8466            retVal += thisApi.GenericAmountInputAmounts[x];
8467            
8468        return retVal;
8469
8470    };
8471    Object.defineProperty(thisApi, 'GenericAmountInputsAmount', { get: function () { return _getGenericAmountInputAmount(); } });
8472
8473    Object.defineProperty(thisApi, 'FirstGenericAmountInputField', { get: function () { let retVal = null; if (thisApi.GenericAmountInputFields.length > 0) retVal = thisApi.GenericAmountInputFields[0]; return retVal; } });
8474    Object.defineProperty(thisApi, 'FirstGenericAmountInputAmount', { get: function () { return _tryGetFieldValueOrZero(thisApi.FirstGenericAmountInputField); } });
8475
8476    //deprecated in favor of FirstGenericAmountInputField to support transparent multi-other amounts with clear dividion of business logic between things like additional donation and membership payment (11.30.25)
8477    //thisApi.PaymentAmountField = _getInitializedFieldApi(_isGenericAmountInputField, function (rawVal) { return rawVal.toDecimalZeroIfInvalid(); }, function () { _handle_XXX_PaymentAmountChanged(); });
8478    //Object.defineProperty(thisApi, 'PaymentAmount', { get: function () { return _tryGetFieldValueOrZero(thisApi.PaymentAmountField); } });
8479
8480    const _isAnyAmountInputField = function (fieldConfig) {
8481        return (
8482            (fieldConfig.DataMapping.TransactionAmount ||
8483                fieldConfig.DataMapping.TaxDeductibleAmount ||
8484                fieldConfig.DataMapping.RecurringAmount ||
8485                fieldConfig.DataMapping.RecurringTaxDeductibleAmount ||
8486                fieldConfig.DataMapping.RecurringMaxAmount)
8487            && fieldConfig.Type.Id != FORM_FIELD_TYPES.TICKET_QUANTITY_SYSTEM
8488        );
8489    }
8490
8491    const _getGenericTaxDeductibleAmountInputFields = function () {
8492
8493        let retVal = [];
8494
8495        for (let x = 0; x < _allFieldApis.length; x++)
8496            if (_allFieldApis[x].Configuration.DataMapping.TaxDeductibleAmount && _allFieldApis[x].Configuration.Type.Id != FORM_FIELD_TYPES.TICKET_QUANTITY_SYSTEM) {
8497
8498                _allFieldApis[x].InitializeDefaultGetReturnValueFunction(function (rawVal) { return rawVal.toDecimalZeroIfInvalid(); });
8499                //_allFieldApis[x].InitializeSetValueCallBackFunction(function () { _handle_XXX_PaymentAmountChanged(); });
8500
8501                retVal.push(_allFieldApis[x]);
8502                 
8503            }
8504
8505        return retVal;
8506
8507    };
8508    Object.defineProperty(thisApi, 'GenericTaxDeductibleAmountInputFields', { get: function () { return _getGenericTaxDeductibleAmountInputFields(); } });
8509
8510    const _getGenericTaxDeductibleAmountInputAmounts = function () {
8511
8512        let retVal = [];
8513
8514        for (let x = 0; x < thisApi.GenericTaxDeductibleAmountInputFields.length; x++)
8515            retVal.push(thisApi.GenericTaxDeductibleAmountInputFields[x].Value);
8516            
8517        return retVal;
8518
8519    };
8520
8521    Object.defineProperty(thisApi, 'GenericTaxDeductibleAmountInputAmounts', { get: function () { return _getGenericTaxDeductibleAmountInputAmounts(); } });
8522
8523    const _getGenericTaxDeductibleAmountInputAmount = function () {
8524
8525        let retVal = 0.00;
8526
8527        for (let x = 0; x < thisApi.GenericTaxDeductibleAmountInputAmounts.length; x++)
8528            retVal += thisApi.GenericTaxDeductibleAmountInputAmounts[x];
8529            
8530        return retVal;
8531
8532    };
8533    Object.defineProperty(thisApi, 'GenericTaxDeductibleAmountInputsAmount', { get: function () { return _getGenericTaxDeductibleAmountInputAmount(); } });
8534
8535    Object.defineProperty(thisApi, 'FirstGenericTaxDeductibleAmountInputField', { get: function () { let retVal = null; if (thisApi.GenericTaxDeductibleAmountInputFields.length > 0) retVal = thisApi.GenericTaxDeductibleAmountInputFields[0]; return retVal; } });
8536    Object.defineProperty(thisApi, 'FirstGenericTaxDeductibleAmountInputAmount', { get: function () { return _tryGetFieldValueOrZero(thisApi.FirstGenericTaxDeductibleAmountInputField); } });
8537
8538    //deprecated in favor of FirstGenericTaxDeductibleAmountInputField to support transparent multi-other amounts with clear dividion of business logic between things like additional donation and membership payment (11.30.25)
8539    //thisApi.PaymentTaxDeductibleAmountField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.TaxDeductibleAmount; }, function (rawVal) { return rawVal.toDecimalZeroIfInvalid(); });
8540    //Object.defineProperty(thisApi, 'PaymentTaxDeductibleAmount', { get: function () { return _tryGetFieldValueOrZero(thisApi.PaymentTaxDeductibleAmountField); } });        
8541
8542    const _isPaymentCurrencyField = function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.CURRENCY_SYSTEM); };
8543
8544    thisApi.PaymentCurrencyField = _getInitializedFieldApi(_isPaymentCurrencyField,
8545        function (rawVal) {
8546
8547            let retVal = rawVal.toIntZeroIfInvalid();
8548
8549            return retVal;
8550
8551        },
8552        function () { _handleCurrencyChanged(); });
8553    Object.defineProperty(thisApi, 'PaymentCurrencyId', { get: function () { return _tryGetFieldValueOrZero(thisApi.PaymentCurrencyField); } });
8554    Object.defineProperty(thisApi, 'PaymentCurrencyCode', { get: function () { return _tryGetFieldText(thisApi.PaymentCurrencyField); } });
8555
8556    thisApi.RecurringPaymentAmountField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.RecurringAmount; }, function (rawVal) { return rawVal.toDecimalZeroIfInvalid(); }); //OVERRIDING ONE-TIME MAPPING, need better solution , function () { _handleRecurringPaymentAmountChanged(); }
8557    Object.defineProperty(thisApi, 'RecurringPaymentAmount', { get: function () { return thisApi.RecurringPaymentAmountField.Value; } });
8558
8559    thisApi.RecurringPaymentTaxDeductibleAmountField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.RecurringTaxDeductibleAmount; }, function (rawVal) { return rawVal.toDecimalZeroIfInvalid(); });
8560    Object.defineProperty(thisApi, 'RecurringPaymentTaxDeductibleAmount', { get: function () { return _tryGetFieldValueOrZero(thisApi.RecurringPaymentTaxDeductibleAmountField); } });
8561
8562    thisApi.RecurringPaymentStartDateField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.RecurringStartDate; }, function (rawVal) { return rawVal.toDateTimeOrNull(); });
8563    Object.defineProperty(thisApi, 'RecurringPaymentStartDate', { get: function () { return _tryGetFieldDateTimeValue(thisApi.RecurringPaymentStartDateField); } });
8564    
8565    Object.defineProperty(thisApi, 'RecurringPaymentStartDateAjaxFormat', { get: function () { return _tryGetFieldDotNetDateTimeValue(thisApi.RecurringPaymentStartDateField); } });
8566
8567    thisApi.RecurringPaymentEndDateField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.RecurringEndDate; }, function (rawVal) { return rawVal.toDateTimeOrNull(); });
8568    Object.defineProperty(thisApi, 'RecurringPaymentEndDate', { get: function () { return _tryGetFieldDateTimeValue(thisApi.RecurringPaymentEndDateField); } });
8569
8570    Object.defineProperty(thisApi, 'RecurringPaymentEndDateAjaxFormat', { get: function () { return _tryGetFieldDotNetDateTimeValue(thisApi.RecurringPaymentEndDateField); } });
8571
8572    thisApi.RecurringMaxAmountField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.RecurringMaxAmount; }, function (rawVal) { return rawVal.toDecimalZeroIfInvalid(); });
8573    Object.defineProperty(thisApi, 'RecurringMaxAmount', { get: function () { return _tryGetFieldValueOrZero(thisApi.RecurringMaxAmountField); } });
8574     
8575    const _isRecurringFrequencyField = function (fieldConfig) { return fieldConfig.DataMapping.RecurringFrequency; };
8576
8577    thisApi.RecurringPaymentFrequencyField = _getInitializedFieldApi(_isRecurringFrequencyField, function (rawVal) { return rawVal.toIntZeroIfInvalid(); }, function () { _handlePaymentFrequencyChanged(); });
8578    Object.defineProperty(thisApi, 'RecurringPaymentFrequencyId', { get: function () { return _tryGetFieldValueOrZero(thisApi.RecurringPaymentFrequencyField); } });    
8579
8580    const _isPaymentMethodField = function (fieldConfig) { return fieldConfig.DataMapping.PaymentMethod; };
8581
8582    thisApi.PaymentMethodField = _getInitializedFieldApi(_isPaymentMethodField, function (rawVal) { return rawVal.toIntZeroIfInvalid(); }, function () { _handlePaymentMethodChanged(); });
8583    Object.defineProperty(thisApi, 'PaymentMethodId', { get: function () { return _tryGetFieldValueOrZero(thisApi.PaymentMethodField); } });
8584
8585    const _getAvailablePaymentMethodIds = function () {
8586
8587        let retVal = [];
8588
8589        if (thisApi.PaymentMethodField != null) {
8590
8591            if (thisApi.PaymentMethodField.HasOptions)
8592                retVal = thisApi.PaymentMethodField.OptionIdValues;
8593            else
8594                retVal.push(thisApi.PaymentMethodField.Value);
8595
8596        }        
8597
8598        return retVal;
8599
8600    };
8601    Object.defineProperty(thisApi, 'AvailablePaymentMethodIds', { get: function () { return _getAvailablePaymentMethodIds(); } });
8602
8603    thisApi.CreditCardTypeField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.CREDIT_CARD_TYPES_SYSTEM || fieldConfig.DataMapping.CreditCardType); }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
8604
8605    thisApi.CreditCardNumberField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.CreditCardNumber; });
8606    Object.defineProperty(thisApi, 'CreditCardNumber', { get: function () { return _tryGetFieldValue(thisApi.CreditCardNumberField); } });
8607
8608    thisApi.CreditCardHolderNameField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.CreditCardNameOnAccount; });
8609    Object.defineProperty(thisApi, 'CreditCardHolderName', { get: function () { return _tryGetFieldValue(thisApi.CreditCardHolderNameField); } });
8610     
8611    thisApi.CreditCardExpirationDateField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.CREDIT_CARD_EXPIRATION_DATE); });
8612    Object.defineProperty(thisApi, 'CreditCardExpirationDate', { get: function () { return _tryGetFieldValue(thisApi.CreditCardExpirationDateField); } });
8613
8614    thisApi.CreditCardExpirationMonthField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == fieldConfig.DataMapping.CREDIT_CARD_EXP_MONTHS_SYSTEM || fieldConfig.DataMapping.CreditCardExpirationMonth); });
8615    Object.defineProperty(thisApi, 'CreditCardExpirationMonth', { get: function () { return _tryGetFieldValue(thisApi.CreditCardExpirationMonthField); } });
8616
8617    thisApi.CreditCardExpirationYearField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == fieldConfig.DataMapping.CREDIT_CARD_EXP_YEARS_SYSTEM || fieldConfig.DataMapping.CreditCardExpirationYear); });
8618    Object.defineProperty(thisApi, 'CreditCardExpirationYear', { get: function () { return _tryGetFieldValue(thisApi.CreditCardExpirationYearField); } });
8619
8620    thisApi.CreditCardSecurityCodeField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.CreditCardSecurityCode; });
8621    Object.defineProperty(thisApi, 'CreditCardSecurityCode', { get: function () { return _tryGetFieldValue(thisApi.CreditCardSecurityCodeField); } });
8622
8623    thisApi.BankAccountNumberField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.BankAccountNumber; });
8624    Object.defineProperty(thisApi, 'BankAccountNumber', { get: function () { return _tryGetFieldValue(thisApi.BankAccountNumberField); } });
8625
8626    thisApi.BankAccountTypeField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.BankAccountType; });
8627    Object.defineProperty(thisApi, 'BankAccountTypeId', { get: function () { return _tryGetFieldValueOrZero(thisApi.BankAccountTypeField); } });
8628
8629    thisApi.BankRoutingNumberField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.BankRoutingNumber; });
8630    Object.defineProperty(thisApi, 'BankRoutingNumber', { get: function () { return _tryGetFieldValue(thisApi.BankRoutingNumberField); } });
8631
8632    thisApi.BankAccountNameOnAccountField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.BankAccountNameOnAccount; });
8633    Object.defineProperty(thisApi, 'BankAccountNameOnAccount', { get: function () { return _tryGetFieldValue(thisApi.BankAccountNameOnAccountField); } });
8634
8635    thisApi.ACHTransactionTypeField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.ACHTransactionType; }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
8636    thisApi.BankNameField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.BankName; });
8637
8638    //InKind
8639    thisApi.InKindTypeField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.InKindType; }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); }, function () { _handleInKindTypeChanged(); });
8640    Object.defineProperty(thisApi, 'InKindTypeId', { get: function () { return _tryGetFieldValueOrZero(thisApi.InKindTypeField); } });        
8641
8642    const _tryGetInkindClassId = function () {
8643
8644        let retVal = IN_KIND_ASSET_CLASSES.NONE;
8645
8646        if (thisApi.InKindTypeId > 0)
8647            retVal = App.InKindTypes.GetClassIdByTypeId(thisApi.InKindTypeId);                     
8648
8649        return retVal;
8650
8651    };
8652
8653    //IN_KIND_ASSET_CLASSES
8654    Object.defineProperty(thisApi, 'InKindTypeClassId', { get: function () { return _tryGetInkindClassId(); } });  
8655
8656    //Framework added    
8657    Object.defineProperty(thisApi, 'InKindVehicleYear', { get: function () { return _tryGetDomInputIntegerValueOrZero(document.getElementById('inKindVehicleYear')); } });
8658    Object.defineProperty(thisApi, 'InKindVehicleMake', { get: function () { return _tryGetDomInputTextValueOrNull(document.getElementById('inKindVehicleMake')); } });
8659    Object.defineProperty(thisApi, 'InKindVehicleMileage', { get: function () { return _tryGetDomInputIntegerValueOrZero(document.getElementById('inKindeVehicleMileage')); } });
8660    Object.defineProperty(thisApi, 'InKindVehicleVin', { get: function () { return _tryGetDomInputTextValueOrNull(document.getElementById('inKindVinNumber')); } });
8661
8662    Object.defineProperty(thisApi, 'InKindStockTicker', { get: function () { return _tryGetDomInputTextValueOrNull(document.getElementById('inKindStockTicker')); } });
8663    Object.defineProperty(thisApi, 'InKindStockCompany', { get: function () { return _tryGetDomInputTextValueOrNull(document.getElementById('inKindStockCompany')); } });
8664
8665    Object.defineProperty(thisApi, 'InKindFoodWeight', { get: function () { return _tryGetDomInputDecimalValueOrZero(document.getElementById('inKindFoodWeight')); } });
8666      
8667    const _isContactTypeField = function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.CONTACT_TYPE_SYSTEM); };
8668
8669    thisApi.ContactTypeField = _getInitializedFieldApi(_isContactTypeField,
8670        function (rawVal) {
8671
8672            let retVal = rawVal.toIntZeroIfInvalid();
8673
8674            if (retVal <= 0)
8675                retVal = CONTACT_TYPES.Person;
8676
8677            return retVal;
8678
8679        },
8680        function () { _handleContactTypeChanged(); });
8681    Object.defineProperty(thisApi, 'ContactTypeId', { get: function () { let retVal = _tryGetFieldValueOrZero(thisApi.ContactTypeField); if (retVal <= 0) retVal = CONTACT_TYPES.Person; return retVal; } });
8682
8683    thisApi.OrganizationNameField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.OrganizationName; });
8684    Object.defineProperty(thisApi, 'OrganizationName', { get: function () { return _tryGetFieldValue(thisApi.OrganizationNameField); } });
8685
8686    thisApi.ContactFirstNameField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.DonorFirstName; });
8687    Object.defineProperty(thisApi, 'ContactFirstName', { get: function () { return _tryGetFieldValue(thisApi.ContactFirstNameField); } });
8688
8689    thisApi.ContactMiddleNameField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.DonorMiddleName; });
8690    Object.defineProperty(thisApi, 'ContactMiddleName', { get: function () { return _tryGetFieldValue(thisApi.ContactMiddleNameField); } });
8691
8692    thisApi.ContactLastNameField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.DonorLastName; });
8693    Object.defineProperty(thisApi, 'ContactLastName', { get: function () { return _tryGetFieldValue(thisApi.ContactLastNameField); } });
8694
8695    thisApi.ContactEmailAddressField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.DonorEmailAddress; });
8696    Object.defineProperty(thisApi, 'ContactEmailAddress', { get: function () { return _tryGetFieldValue(thisApi.ContactEmailAddressField); } });
8697
8698    thisApi.ContactPhoneNumberField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.DonorPhoneNumber; });
8699    Object.defineProperty(thisApi, 'ContactPhoneNumber', { get: function () { return _tryGetFieldValue(thisApi.ContactPhoneNumberField); } });
8700
8701    //framework injected
8702    const _tryGetContactAddressSingleLineEntryDomElement = function () { return document.getElementById('contactAddressSingleLineInput'); };
8703    const _tryGetContactAddressSingleLineEntryValue = function () { return _tryGetDomInputTextValueOrNull(_tryGetContactAddressSingleLineEntryDomElement()); }; 
8704     
8705    thisApi.ContactAddressStreet1Field = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.DonorAddressStreet1; }, null, null, function() { _showAllContactAddressFields(); });
8706    Object.defineProperty(thisApi, 'ContactAddressStreet1', { get: function () { return _tryGetFieldValue(thisApi.ContactAddressStreet1Field); } });
8707
8708    thisApi.ContactAddressStreet2Field = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.DonorAddressStreet2; }, null, null, function () { _showAllContactAddressFields(); });
8709    Object.defineProperty(thisApi, 'ContactAddressStreet2', { get: function () { return _tryGetFieldValue(thisApi.ContactAddressStreet2Field); } });
8710
8711    thisApi.ContactAddressCityField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.DonorAddressCity; }, null, null, function () { _showAllContactAddressFields(); });
8712    Object.defineProperty(thisApi, 'ContactAddressCity', { get: function () { return _tryGetFieldValue(thisApi.ContactAddressCityField); } });
8713
8714    thisApi.ContactAddressStateProvinceField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.DonorAddressState; }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); }, null, function () { _showAllContactAddressFields(); });
8715    Object.defineProperty(thisApi, 'ContactAddressStateProvinceId', { get: function () { return _tryGetFieldValueOrZero(thisApi.ContactAddressStateProvinceField); } });
8716    Object.defineProperty(thisApi, 'ContactAddressStateProvinceCode', { get: function () { return _tryGetFieldText(thisApi.ContactAddressStateProvinceField); } });
8717
8718    thisApi.ContactAddressPostalCodeField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.DonorAddressPostalCode; }, null, null, function () { _showAllContactAddressFields(); });
8719    Object.defineProperty(thisApi, 'ContactAddressPostalCode', { get: function () { return _tryGetFieldValue(thisApi.ContactAddressPostalCodeField); } });
8720
8721    thisApi.ContactAddressCountryField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.DonorAddressCountry; }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); }, null, function () { _showAllContactAddressFields(); });
8722    Object.defineProperty(thisApi, 'ContactAddressCountryId', { get: function () { return _tryGetFieldValueOrZero(thisApi.ContactAddressCountryField); } });
8723    Object.defineProperty(thisApi, 'ContactAddressCountryCode', { get: function () { return App.Countries.GetCountryCodeByIdOrUSA(_tryGetFieldValueOrZero(thisApi.ContactAddressCountryField)); } });
8724     
8725    thisApi.EmailOptInField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.EmailOptIn; });
8726    Object.defineProperty(thisApi, 'EmailOptIn', { get: function () { return _tryGetFieldBoolValueOrNull(thisApi.EmailOptInField); } }, function (rawVal) { return rawVal.toBoolFalseIfInvalid(); });
8727
8728    thisApi.MailOptInField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.MailOptIn; });
8729    Object.defineProperty(thisApi, 'MailOptIn', { get: function () { return _tryGetFieldBoolValueOrNull(thisApi.MailOptInField); } }, function (rawVal) { return rawVal.toBoolFalseIfInvalid(); });
8730
8731    thisApi.PhoneOptInField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.PhoneOptIn; });
8732    Object.defineProperty(thisApi, 'PhoneOptIn', { get: function () { return _tryGetFieldBoolValueOrNull(thisApi.PhoneOptInField); } }, function (rawVal) { return rawVal.toBoolFalseIfInvalid(); });
8733
8734    thisApi.SmsOptInField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.SmsOptIn; });
8735    Object.defineProperty(thisApi, 'SmsOptIn', { get: function () { return _tryGetFieldBoolValueOrNull(thisApi.SmsOptInField); } }, function (rawVal) { return rawVal.toBoolFalseIfInvalid(); });
8736     
8737    const _getSelectedEmailOptInListIds = function () {
8738
8739        let retVal = [];
8740
8741        if (thisApi.EmailOptInListsField != null) {
8742
8743            if (thisApi.EmailOptInListsField.HasOptions)
8744                retVal = thisApi.EmailOptInListsField.SelectedOptionIdValues;
8745            else
8746                retVal.push(thisApi.EmailOptInListsField.Value);
8747
8748        }        
8749
8750        return retVal;
8751
8752    };
8753    Object.defineProperty(thisApi, 'SelectedEmailOptInListIds', { get: function () { return _getSelectedEmailOptInListIds(); } });
8754
8755    thisApi.EmailOptInListsField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.EMAIL_OPTIN_LIST_SYSTEM); }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
8756    Object.defineProperty(thisApi, 'EmailOptInListId', { get: function () { return _tryGetFieldValueOrZero(thisApi.EmailOptInListsField); } });
8757     
8758    const _getSelectedVolunteerPostIds = function () {
8759
8760        let retVal = [];
8761
8762        if (thisApi.VolunteerPostsField != null) {
8763
8764            if (thisApi.VolunteerPostsField.HasOptions)
8765                retVal = thisApi.VolunteerPostsField.SelectedOptionIdValues;
8766            else
8767                retVal.push(thisApi.VolunteerPostsField.Value);
8768
8769        }        
8770
8771        return retVal;
8772
8773    };
8774    Object.defineProperty(thisApi, 'SelectedVolunteerPostIds', { get: function () { return _getSelectedVolunteerPostIds(); } });
8775
8776    thisApi.VolunteerPostsField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.VOLUNTEER_POST_SELECTOR_NEW); }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
8777    Object.defineProperty(thisApi, 'VolunteerPostId', { get: function () { return _tryGetFieldValueOrZero(thisApi.VolunteerPostsField); } });
8778
8779    const _getVolunteerTimeEntryFields = function () {
8780
8781        let retVal = [];
8782
8783        for (let x = 0; x < _allFieldApis.length; x++)
8784            if (_allFieldApis[x].Configuration.Type.Id == FORM_FIELD_TYPES.VOLUNTEER_POST_TIME_ENTRY) {
8785
8786                //time may be configured to hide entirely so was never rendered - nothing to wire up
8787                if (_allFieldApis[x].DomElement == null)
8788                    continue;
8789
8790                //_allFieldApis[x].InitializeDefaultGetReturnValueFunction(function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
8791                //_allFieldApis[x].InitializeSetValueCallBackFunction(_handleVolunteerTimeEntryChanged);
8792
8793                retVal.push(_allFieldApis[x]);
8794
8795            }
8796
8797        return retVal;
8798
8799    };
8800
8801    Object.defineProperty(thisApi, 'VolunteerTimeEntryFields', {
8802        get: function () {
8803            return _getVolunteerTimeEntryFields();
8804        }
8805    });
8806
8807    const _getSelectedCrmGroupIds = function () {
8808
8809        let retVal = [];
8810
8811        if (thisApi.CrmGroupsField != null) {
8812
8813            if (thisApi.CrmGroupsField.HasOptions)
8814                retVal = thisApi.CrmGroupsField.SelectedOptionIdValues;
8815            else
8816                retVal.push(thisApi.CrmGroupsField.Value);
8817
8818        }        
8819
8820        return retVal;
8821
8822    };
8823    Object.defineProperty(thisApi, 'SelectedCrmGroupIds', { get: function () { return _getSelectedCrmGroupIds(); } });
8824
8825    thisApi.CrmGroupsField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.CRM_GROUP_SELECTOR); }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
8826    Object.defineProperty(thisApi, 'CrmGroupId', { get: function () { return _tryGetFieldValueOrZero(thisApi.CrmGroupsField); } });
8827     
8828    thisApi.BillingAddressStreet1Field = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.BillingAddressStreet1; });
8829    Object.defineProperty(thisApi, 'BillingAddressStreet1', { get: function () { return _tryGetFieldValue(thisApi.BillingAddressStreet1Field); } });
8830
8831    thisApi.BillingAddressStreet2Field = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.BillingAddressStreet2; });
8832    Object.defineProperty(thisApi, 'BillingAddressStreet2', { get: function () { return _tryGetFieldValue(thisApi.BillingAddressStreet2Field); } });
8833
8834    thisApi.BillingAddressCityField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.BillingAddressCity; });
8835    Object.defineProperty(thisApi, 'BillingAddressCity', { get: function () { return _tryGetFieldValue(thisApi.BillingAddressCityField); } });
8836
8837    thisApi.BillingAddressStateProvinceField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.BillingAddressState; }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
8838    Object.defineProperty(thisApi, 'BillingAddressStateProvinceCode', { get: function () { return _tryGetFieldText(thisApi.BillingAddressStateProvinceField); } });
8839
8840    thisApi.BillingAddressPostalCodeField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.BillingAddressPostalCode; });
8841    Object.defineProperty(thisApi, 'BillingAddressPostalCode', { get: function () { return _tryGetFieldValue(thisApi.BillingAddressPostalCodeField); } });
8842
8843    thisApi.BillingAddressCountryField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.BillingAddressCountry; }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
8844    Object.defineProperty(thisApi, 'BillingAddressCountryCode', { get: function () { return App.Countries.GetCountryCodeByIdOrUSA(_tryGetFieldValueOrZero(thisApi.BillingAddressCountryField)); } });
8845
8846    thisApi.FundField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.FUND_SYSTEM); }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
8847    Object.defineProperty(thisApi, 'FundId', { get: function () { return _tryGetFieldValueOrZero(thisApi.FundField); } });
8848
8849    thisApi.GrassrootsEventField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.GRASSROOTS_EVENT_ID_SYSTEM); }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
8850    Object.defineProperty(thisApi, 'GrassrootsEventId', { get: function () { return _tryGetFieldValueOrZero(thisApi.GrassrootsEventField); } });
8851     
8852    thisApi.PromoCodeField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.PROMO_CODE_ADVANCED_SYSTEM); });
8853
8854    thisApi.CommentField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.COMMENT_SYSTEM); });
8855    Object.defineProperty(thisApi, 'Comment', { get: function () { return _tryGetFieldValue(thisApi.CommentField); } });
8856
8857    thisApi.AnonymousField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.Anonymous; });
8858    Object.defineProperty(thisApi, 'Anonymous', { get: function () { return _tryGetFieldValueOrFalse(thisApi.AnonymousField); } }, function (rawVal) { return rawVal.toBoolFalseIfInvalid(); });
8859
8860    const _getTicketQuantityFields = function () {
8861
8862        let retVal = [];
8863
8864        for (let x = 0; x < _allFieldApis.length; x++)
8865            if (_allFieldApis[x].Configuration.Type.Id == FORM_FIELD_TYPES.TICKET_QUANTITY_SYSTEM) {
8866
8867                //ticket may be configured to hide entirely (sold out / outside its sale window) and s
8867o was never rendered - nothing to wire up
8868                if (_allFieldApis[x].DomElement == null)
8869                    continue;
8870
8871                //ticket is sold out or outside its sale window - rendered as a plain message instead of a quantity control (avoids reading .Value on a non-input element)
8872                if (_allFieldApis[x].DomElement.hasClassName('ce-ticketUnavailableMessages'))
8873                    continue;
8874
8875                _allFieldApis[x].InitializeDefaultGetReturnValueFunction(function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
8876                _allFieldApis[x].InitializeSetValueCallBackFunction(_handleTicketQuantityChanged);
8877
8878                retVal.push(_allFieldApis[x]);
8879
8880            }
8881
8882        return retVal;
8883
8884    };
8885
8886    Object.defineProperty(thisApi, 'TicketQuantityFields', {
8887        get: function () {
8888            return _getTicketQuantityFields();
8889        }
8890    });
8891
8892    const _getTicketSessionFieldsApis = function () {
8893
8894        let retVal = [];
8895
8896        for (let x = 0; x < _allFieldApis.length; x++)
8897            if (_allFieldApis[x].Configuration.Type.Id == FORM_FIELD_TYPES.TICKET_SESSION_SYSTEM && _allFieldApis[x].DomElement != null) {
8898                retVal.push(_allFieldApis[x]);
8899                break;
8900            }
8901
8902        return retVal;
8903
8904    };
8905
8906    Object.defineProperty(thisApi, 'TicketSessionFields', {
8907        get: function () {
8908            return _getTicketSessionFieldsApis();
8909        }
8910    });
8911
8912    const _getTicketSubTotalFields = function () {
8913
8914        let retVal = [];
8915
8916        for (let x = 0; x < _allFieldApis.length; x++)
8917            if (_allFieldApis[x].Configuration.Type.Id == FORM_FIELD_TYPES.TICKET_SUBTOTAL_SYSTEM) {
8918
8919                //ticket may be configured to hide entirely (sold out / outside its sale window) and so was never rendered
8920                if (_allFieldApis[x].DomElement == null)
8921                    continue;
8922
8923                _allFieldApis[x].InitializeDefaultGetReturnValueFunction(function (rawVal) { return rawVal.toDecimalZeroIfInvalid(); });
8924
8925                retVal.push(_allFieldApis[x]);
8926
8927            }
8928
8929        return retVal;
8930
8931    };
8932
8933    Object.defineProperty(thisApi, 'TicketSubTotalFields', {
8934        get: function () {
8935            return _getTicketSubTotalFields();
8936        }
8937    });
8938      
8939    thisApi.DietaryRestrictionField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.DietaryRestrictions; });
8940    Object.defineProperty(thisApi, 'DietaryRestriction', { get: function () { return _tryGetFieldValue(thisApi.DietaryRestrictionField); } });
8941
8942    thisApi.EmergencyContactNameField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.EmergencyContactName; });
8943    Object.defineProperty(thisApi, 'EmergencyContactName', { get: function () { return _tryGetFieldValue(thisApi.EmergencyContactNameField); } });
8944
8945    thisApi.EmergencyContactPhoneNumberField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.EmergencyContactPhoneNumber; });
8946    Object.defineProperty(thisApi, 'EmergencyContactPhoneNumber', { get: function () { return _tryGetFieldValue(thisApi.EmergencyContactPhoneNumberField); } });
8947     
8948    thisApi.CaseTitleField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.CaseTitle; });
8949    Object.defineProperty(thisApi, 'CaseTitle', { get: function () { return _tryGetFieldValue(thisApi.CaseTitleField); } });
8950
8951    thisApi.CaseStatusField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.CaseStatus; }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
8952    Object.defineProperty(thisApi, 'CaseStatusId', { get: function () { return _tryGetFieldValueOrZero(thisApi.CaseStatusField); } });
8953
8954    thisApi.CasePriorityField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.CasePriority; }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
8955    Object.defineProperty(thisApi, 'CasePriorityId', { get: function () { return _tryGetFieldValueOrZero(thisApi.CasePriorityField); } });
8956
8957    thisApi.CaseDescriptionField = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.CaseDescription; });
8958    Object.defineProperty(thisApi, 'CaseDescription', { get: function () { return _tryGetFieldValue(thisApi.CaseDescriptionField); } });
8959     
8960    thisApi.FreeGiftSelectorField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.FREE_GIFT_SELECTOR); }); //not sure this will work , function (rawVal) { return rawVal.toIntZeroIfInvalid(); } - do we need a processing function for more complex types like this?
8961    Object.defineProperty(thisApi, 'FreeGiftSelectorProductId', {
8962        get: function () {
8963
8964            let retVal = 0;
8965
8966            if (thisApi.FreeGiftSelectorField != null && thisApi.FreeGiftSelectorField.DomElement.querySelector('input[type="radio"]:checked') != null)
8967                retVal = thisApi.FreeGiftSelectorField.DomElement.querySelector('input[type="radio"]:checked').value.toIntZeroIfInvalid();
8968
8969            return retVal; 
8970
8971        }
8972    });                
8973
8974    Object.defineProperty(thisApi, 'FreeGiftSelectorfulfillmentCenterId', {
8975        get: function () {
8976
8977            let retVal = 0;
8978
8979            if (thisApi.FreeGiftSelectorField != null)
8980                retVal = thisApi.FreeGiftSelectorField.DomElement.tryGetIntegerAttribute('data-fulfillment-center-id');
8981
8982            return retVal; 
8983
8984        }
8985    });    
8986
8987    thisApi.MembershipTypeField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.MEMBERSHIP_TYPES_SYSTEM); }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); }, function () { _handleMembershipTypeChanged(); });
8988    Object.defineProperty(thisApi, 'MembershipTypeId', { get: function () { return _tryGetFieldValueOrZero(thisApi.MembershipTypeField); } });                    
8989
8990    //Framework added
8991    Object.defineProperty(thisApi, 'AutoRenewMembership', { get: function () { let retVal = false; if (document.getElementById('autoRenewMembershipCheckBox_0') != null) retVal = document.getElementById('autoRenewMembershipCheckBox_0').checked; return retVal; } });
8992    
8993    thisApi.PledgeTypeField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.PLEDGE_TYPES_SYSTEM); }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
8994    Object.defineProperty(thisApi, 'PledgeTypeId', { get: function () { return _tryGetFieldValueOrZero(thisApi.PledgeTypeField); } });                    
8995
8996    thisApi.TributeField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ID_SYSTEM); }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
8997    Object.defineProperty(thisApi, 'TributeId', { get: function () { return _tryGetFieldValueOrZero(thisApi.TributeField); } });
8998     
8999    thisApi.TributeTypeField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.TRIBUTE_TYPE_SYSTEM); }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
9000    Object.defineProperty(thisApi, 'TributeTypeId', { get: function () { return _tryGetFieldValueOrZero(thisApi.TributeTypeField); } });
9001
9002    thisApi.TributeNameField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.TRIBUTE_NAME_SYSTEM); });
9003    Object.defineProperty(thisApi, 'TributeName', { get: function () { return _tryGetFieldValue(thisApi.TributeNameField); } });
9004
9005    thisApi.TributeDescriptionField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.TRIBUTE_DESCRIPTION_SYSTEM); });
9006    Object.defineProperty(thisApi, 'TributeDescription', { get: function () { return _tryGetFieldValue(thisApi.TributeDescriptionField); } });
9007
9008    //TRIBUTE_PEN_NAME_SYSTEM: 27,
9009    //TRIBUTE_ANONYMOUS_SYSTEM: 28,
9010    //TRIBUTE_START_DATE_SYSTEM: 29,
9011    //TRIBUTE_END_DATE_SYSTEM: 30,
9012    //TRIBUTE_GOAL_SYSTEM: 31,
9013
9014    thisApi.TributePersonalMessageField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_PERSONAL_MESSAGE_SYSTEM); });
9015    Object.defineProperty(thisApi, 'TributePersonalMessage', { get: function () { return _tryGetFieldValue(thisApi.TributePersonalMessageField); } });
9016
9017    //TRIBUTE_ACK_NAME_PREFIX__SYSTEM: 33,
9018
9019    thisApi.TributeRecipientFirstNameField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_FIRST_NAME_SYSTEM); });
9020    Object.defineProperty(thisApi, 'TributeRecipientFirstName', { get: function () { return _tryGetFieldValue(thisApi.TributeRecipientFirstNameField); } });
9021
9022    //TRIBUTE_ACK_MIDDLE_NAME_SYSTEM: 35,
9023
9024    thisApi.TributeRecipientLastNameField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_LAST_NAME_SYSTEM); });
9025    Object.defineProperty(thisApi, 'TributeRecipientLastName', { get: function () { return _tryGetFieldValue(thisApi.TributeRecipientLastNameField); } });
9026
9027    //TRIBUTE_ACK_NAME_SUFFIX_SYSTEM: 37,
9028    //TRIBUTE_ACK_TITLE_SYSTEM: 38,
9029
9030    const _tryGetTributeRecipientAddressSingleLineEntryDomElement = function () { return document.getElementById('tributeAddressSingleLineInput'); };
9031    const _tryGetTributeRecipientAddressSingleLineEntryValue = function () { return _tryGetDomInputTextValueOrNull(_tryGetTributeRecipientAddressSingleLineEntryDomElement()); }; 
9032
9033    thisApi.TributeRecipientAddressStreet1Field = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_ADDRESS_1_SYSTEM); });
9034    Object.defineProperty(thisApi, 'TributeRecipientAddressStreet1', { get: function () { return _tryGetFieldValue(thisApi.TributeRecipientAddressStreet1Field); } });
9035
9036    thisApi.TributeRecipientAddressStreet2Field = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_ADDRESS_2_SYSTEM); });
9037    Object.defineProperty(thisApi, 'TributeRecipientAddressStreet2', { get: function () { return _tryGetFieldValue(thisApi.TributeRecipientAddressStreet2Field); } });
9038
9039    //TRIBUTE_ACK_ADDRESS_3_SYSTEM: 43,
9040    //TRIBUTE_ACK_ADDRESS_4_SYSTEM: 44,
9041
9042    thisApi.TributeRecipientAddressCityField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_CITY_SYSTEM); });
9043    Object.defineProperty(thisApi, 'TributeRecipientAddressCity', { get: function () { return _tryGetFieldValue(thisApi.TributeRecipientAddressCityField); } });
9044
9045    thisApi.TributeRecipientAddressStateField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_STATE_SYSTEM); });
9046    Object.defineProperty(thisApi, 'TributeRecipientAddressStateCode', { get: function () { return _tryGetFieldText(thisApi.TributeRecipientAddressStateField); } });
9047
9048    thisApi.TributeRecipientAddressPostalCodeField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_POSTAL_CODE_SYSTEM); });
9049    Object.defineProperty(thisApi, 'TributeRecipientAddressPostalCode', { get: function () { return _tryGetFieldValue(thisApi.TributeRecipientAddressPostalCodeField); } });
9050
9051    thisApi.TributeRecipientAddressCountryField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_COUNTRY_SYSTEM); });
9052    Object.defineProperty(thisApi, 'TributeRecipientAddressCountryCode', { get: function () { return _tryGetFieldText(thisApi.TributeRecipientAddressCountryField); } });
9053
9054    thisApi.TributeRecipientPhoneNumberField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_PHONE_NUMBER_SYSTEM); });
9055    Object.defineProperty(thisApi, 'TributeRecipientPhoneNumber', { get: function () { return _tryGetFieldValue(thisApi.TributeRecipientPhoneNumberField); } });
9056
9057    thisApi.TributeRecipientEmailAddressField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.TRIBUTE_ACK_EMAIL_SYSTEM); });
9058    Object.defineProperty(thisApi, 'TributeRecipientEmailAddress', { get: function () { return _tryGetFieldValue(thisApi.TributeRecipientEmailAddressField); } });
9059     
9060    thisApi.ECardField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.ECARD_SYSTEM); }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
9061    Object.defineProperty(thisApi, 'ECardId', { get: function () { return _tryGetFieldValueOrZero(thisApi.ECardField); } });
9062
9063    thisApi.PrintCardField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.PRINT_CARD_SYSTEM); }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
9064    Object.defineProperty(thisApi, 'PrintCardId', { get: function () { return _tryGetFieldValueOrZero(thisApi.PrintCardField); } });
9065
9066    thisApi.PrintCardCountField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.PRINT_CARD_COUNT_SYSTEM); }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
9067    Object.defineProperty(thisApi, 'PrintCardCount', { get: function () { return _tryGetFieldValueOrZero(thisApi.PrintCardCountField); } });
9068
9069    thisApi.SolicitorField = _getInitializedFieldApi(function (fieldConfig) { return (fieldConfig.Type.Id == FORM_FIELD_TYPES.SOLICITOR); }, function (rawVal) { return rawVal.toIntZeroIfInvalid(); });
9070    Object.defineProperty(thisApi, 'SolicitorId', { get: function () { return _tryGetFieldValueOrZero(thisApi.SolicitorField); } });      
9071
9072    thisApi.CustomCode1Field = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.CustomCode1; });
9073    Object.defineProperty(thisApi, 'CustomCode1', { get: function () { return _tryGetFieldValue(thisApi.CustomCode1Field); } });
9074
9075    thisApi.CustomCode2Field = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.CustomCode2; });
9076    Object.defineProperty(thisApi, 'CustomCode2', { get: function () { return _tryGetFieldValue(thisApi.CustomCode2Field); } });
9077
9078    thisApi.CustomCode3Field = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.CustomCode3; });
9079    Object.defineProperty(thisApi, 'CustomCode3', { get: function () { return _tryGetFieldValue(thisApi.CustomCode3Field); } });
9080
9081    thisApi.CustomCode4Field = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.CustomCode4; });
9082    Object.defineProperty(thisApi, 'CustomCode4', { get: function () { return _tryGetFieldValue(thisApi.CustomCode4Field); } });
9083
9084    thisApi.CustomCode5Field = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.CustomCode5; });
9085    Object.defineProperty(thisApi, 'CustomCode5', { get: function () { return _tryGetFieldValue(thisApi.CustomCode5Field); } });
9086
9087    thisApi.CustomCode6Field = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.CustomCode6; });
9088    Object.defineProperty(thisApi, 'CustomCode6', { get: function () { return _tryGetFieldValue(thisApi.CustomCode6Field); } });
9089
9090    thisApi.CustomCode7Field = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.CustomCode7; });
9091    Object.defineProperty(thisApi, 'CustomCode7', { get: function () { return _tryGetFieldValue(thisApi.CustomCode7Field); } });
9092
9093    thisApi.CustomCode8Field = _getInitializedFieldApi(function (fieldConfig) { return fieldConfig.DataMapping.CustomCode8; });
9094    Object.defineProperty(thisApi, 'CustomCode8', { get: function () { return _tryGetFieldValue(thisApi.CustomCode8Field); } });
9095
9096    //Core Processing APIs (form & field settings)
9097    //********************************************
9098
9099    //Core Processing APIs (ui helpers)
9100
9101    const _isDafPayPaymentMethodAvailable = function () {
9102
9103        let retVal = false;
9104
9105        for (let x = 0; x < thisApi.AvailablePaymentMethodIds.length; x++)
9106            if (thisApi.AvailablePaymentMethodIds[x] == PAYMENT_METHODS.SOFT_CREDIT) {
9107                retVal = true;
9108                break;
9109            }
9110
9111        if (retVal && thisApi.PaymentSectionConfig != null && !thisApi.PaymentSectionConfig.IncludeDafPay)
9112            retVal = false;
9113             
9114        return retVal;
9115
9116    };
9117    Object.defineProperty(thisApi, 'DafPayPaymentMethodAvailable', { get: function () { return _isDafPayPaymentMethodAvailable(); } });
9118
9119    const _inKindPaymentMethodAvailable = function () {
9120
9121        let retVal = false;
9122
9123        for (let x = 0; x < thisApi.AvailablePaymentMethodIds.length; x++)
9124            if (thisApi.AvailablePaymentMethodIds[x] == PAYMENT_METHODS.IN_KIND) {
9125                retVal = true;
9126                break;
9127            }
9128
9129        return retVal;
9130
9131    };
9132
9133    const _inkindOnlyPaymentForm = function () {
9134        return (thisApi.AvailablePaymentMethodIds.length == 1 && _inKindPaymentMethodAvailable());
9135    };
9136
9137    const _creditCardPaymentMethodAvailable = function () {
9138
9139        let retVal = false;
9140
9141        for (let x = 0; x < thisApi.AvailablePaymentMethodIds.length; x++)
9142            if (thisApi.AvailablePaymentMethodIds[x] == PAYMENT_METHODS.CREDIT_CARD) {
9143                retVal = true;
9144                break;
9145            }
9146
9147        return retVal;
9148
9149    };
9150
9151    const _achPaymentMethodAvailable = function () {
9152
9153        let retVal = false;
9154
9155        for (let x = 0; x < thisApi.AvailablePaymentMethodIds.length; x++)
9156            if (thisApi.AvailablePaymentMethodIds[x] == PAYMENT_METHODS.CREDIT_CARD) {
9157                retVal = true;
9158                break;
9159            }
9160
9161        return retVal;
9162
9163    };
9164     
9165    const _payPalPaymentMethodAvailable = function () {
9166
9167        let retVal = false;
9168
9169        for (let x = 0; x < thisApi.AvailablePaymentMethodIds.length; x++)
9170            if (thisApi.AvailablePaymentMethodIds[x] == PAYMENT_METHODS.PAYPAL) {
9171                retVal = true;
9172                break;
9173            }
9174
9175        return retVal;
9176
9177    };
9178
9179    const _oneClickCompatiblePaymentMethodsAvailable = function () {
9180
9181        let retVal = (_creditCardPaymentMethodAvailable() || _achPaymentMethodAvailable());
9182
9183        return retVal;
9184
9185    };
9186
9187    const _getFirstPaymentMethodBillingInfoField = function () {
9188
9189        let retVal = null;
9190
9191        for (let x = 0; x < _allFieldApis.length; x++)
9192            if (_allFieldApis[x].IsPaymentMethodBillingInformation) {
9193                retVal = _allFieldApis[x];
9194                break;
9195            }
9196
9197        return retVal;
9198
9199    };
9200
9201    const _getFirstTributeRecipientContactInfoField = function () {
9202
9203        let retVal = null;
9204
9205        for (let x = 0; x < _allFieldApis.length; x++)
9206            if (_allFieldApis[x].IsTributeRecipientContactInformation) {
9207                retVal = _allFieldApis[x];
9208                break;
9209            }
9210
9211        return retVal;
9212
9213    };
9214
9215    const _getFirstContactAddressField = function () {
9216
9217        let retVal = null;
9218
9219        for (let x = 0; x < _allFieldApis.length; x++)
9220            if (_allFieldApis[x].IsContactAddressField) {
9221                retVal = _allFieldApis[x];
9222                break;
9223            }
9224
9225        return retVal;
9226
9227    };
9228
9229    const _getFirstEnabledContactAddressField = function () {
9230
9231        let retVal = null;
9232
9233        for (let x = 0; x < _allFieldApis.length; x++)
9234            if (_allFieldApis[x].IsContactAddressField && _allFieldApis[x].Enabled) {
9235                retVal = _allFieldApis[x];
9236                break;
9237            }
9238
9239        return retVal;
9240
9241    };
9242
9243    const _getFileUploadFieldApis = function () {
9244
9245        let retVal = [];
9246
9247        for (let x = 0; x < _allFieldApis.length; x++)
9248            if (_allFieldApis[x].Configuration.Type.Id == FORM_FIELD_TYPES.FILE)
9249                retVal.push(_allFieldApis[x]);
9250
9251        return retVal;
9252
9253    };
9254
9255    const _hideAllPaymentFields = function () {
9256
9257        _tryHideField(thisApi.CreditCardTypeField);
9258        _tryHideField(thisApi.CreditCardNumberField);
9259        _tryHideField(thisApi.CreditCardHolderNameField); 
9260        _tryHideField(thisApi.CreditCardExpirationDateField); 
9261        _tryHideField(thisApi.CreditCardExpirationMonthField);
9262        _tryHideField(thisApi.CreditCardExpirationYearField);
9263        _tryHideField(thisApi.CreditCardSecurityCodeField);
9264
9265        _tryHideField(thisApi.BankAccountNumberField);
9266        _tryHideField(thisApi.BankAccountTypeField);
9267        _tryHideField(thisApi.BankRoutingNumberField);
9268        _tryHideField(thisApi.BankAccountNameOnAccountField);
9269        _tryHideField(thisApi.ACHTransactionTypeField);
9270        _tryHideField(thisApi.BankNameField);
9271
9272        _tryHideInKindTypeDetailFields();
9273
9274        if (document.getElementById('togglePaymentMethodBillingInfoContainer') != null)
9275            document.getElementById('togglePaymentMethodBillingInfoContainer').vanish();
9276
9277        _tryHideField(thisApi.BillingAddressStreet1Field);
9278        _tryHideField(thisApi.BillingAddressStreet2Field);
9279        _tryHideField(thisApi.BillingAddressCityField);
9280        _tryHideField(thisApi.BillingAddressStateProvinceField);
9281        _tryHideField(thisApi.BillingAddressPostalCodeField);
9282        _tryHideField(thisApi.BillingAddressCountryField);
9283
9284        thisApi.HideSubmitButton();
9285
9286        thisApi.PayPalApi.HideButton();
9287        _tryHideGooglePayButton();
9288        _tryHideApplePayButton();
9289        thisApi.DafPayApi.HideButton();
9290
9291    };
9292
9293    const _calculateSelectedPaymentMethodfees = function () {
9294
9295        let retVal = 0.00;
9296         
9297        let payMethId = thisApi.PaymentMethodId;
9298             
9299        switch (payMethId) {
9300            case PAYMENT_METHODS.CREDIT_CARD:
9301            case PAYMENT_METHODS.ACH:
9302            case PAYMENT_METHODS.PAYPAL:
9303            case PAYMENT_METHODS.SOFT_CREDIT:
9304            case PAYMENT_METHODS.ONECLICK_DONATION:
9305
9306                let amt = thisApi.GetDiscountedAmount();
9307                if (amt > 0) {
9308
9309                    let feeRate = WebFormContext.Form.CoverFeeRate;
9310                    //console.log('feeRate: ' + feeRate);
9311                    if (feeRate > 0)
9312                        retVal = (amt * (feeRate / 100));
9313
9314                }
9315
9316                break;
9317        }
9318            
9319        return retVal;
9320
9321    };
9322
9323    const _calculateFees = function () {
9324
9325        let retVal = 0.00;
9326
9327        let cfCb = document.getElementById('coverFeesCheckBox');
9328        if (cfCb != null && cfCb.getValue()) 
9329            retVal = _calculateSelectedPaymentMethodfees();
9330             
9331        return retVal;
9332
9333    };
9334
9335    const _getFeesFormatted = function () {
9336
9337        let amt = _calculateFees();
9338        let sym = _getPaymentCurrencySymbol();
9339
9340        return formatCurrency(amt, true, sym);
9341
9342    };
9343
9344    const _getAmountFormatted = function (amt) {
9345
9346        let sym = _getPaymentCurrencySymbol();
9347
9348        return formatCurrency(amt, true, sym);
9349
9350    };
9351
9352    const _updateSubmitButtonText = function () {
9353
9354        let btnText = WebFormContext.Form.NextButtonLabel;
9355
9356        if (isBlank(btnText))
9357            btnText += 'Submit';
9358
9359        if (WebFormContext.Form.ShowAmountOnNextButton) {
9360
9361            let amt = thisApi.GetDiscountedAmount();
9362
9363            _logger.Log('update button text called with orig amt: ' + amt);
9364
9365            if (amt > 0) {
9366
9367                let fees = _calculateFees();
9368
9369                amt = amt + fees;
9370
9371                btnText += ' ' + _getAmountFormatted(amt);
9372
9373                let freq_id = _tryGetIdFieldValueOrZero(thisApi.RecurringPaymentFrequencyField);
9374                if (freq_id > 0)
9375                    btnText += ' ' + getPaymentFrequencyDescriptionById(freq_id);
9376                     
9377                let donationAmt = thisApi.GenericAmountInputsAmount;
9378
9379                if (donationAmt > 0 && thisApi.GetPreDiscountAmount() > donationAmt)
9380                    btnText += ' (includes ' + _getAmountFormatted(donationAmt) + ' donation)';
9381
9382            }
9383
9384        }
9385
9386        thisApi.SetSubmitButtonText(btnText);
9387
9388    };
9389
9390    const _updateFormAfterAmountImpactingChange = function () {
9391         
9392        if (thisApi.PaymentMethodField != null) {
9393
9394            //check amount to see if payment section should display or not
9395            let amt = thisApi.GetDiscountedAmount();
9396             
9397            if (amt > 0) { 
9398
9399                //special logic for forms like In-kind only, where amount is valid, but there are no fields.
9400                if (_inkindOnlyPaymentForm())
9401                    thisApi.PaymentMethodField.Section.Hide();
9402                else { 
9403                    thisApi.PaymentMethodField.Section.Show();
9404                    _firePaymentMethodSectionShownEvent();
9405                }
9406
9407            }
9408            else
9409                thisApi.PaymentMethodField.Section.Hide();
9410
9411        }
9412
9413        _refreshCoverFeesWidget();
9414
9415        _refreshFreeGiftSectionBasedOnAmountChange();
9416
9417        _updateSubmitButtonText();
9418
9419    };
9420
9421    const _handlePaymentFrequencyChanged = function () {
9422
9423        _refreshPaymentAmountSectionForFormUpdates();
9424
9425        _refreshFreeGiftSectionBasedOnAmountChange();
9426
9427        _updateSubmitButtonText();
9428
9429        _firePaymentRecurringFrequencyChangedEvent();
9430
9431    };
9432
9433    const _handlePaymentAmountChanged = function () {
9434
9435        _logger.Log('payment amount changed handler called');
9436
9437        _refreshPaymentAmountSectionForFormUpdates();
9438
9439        _updateFormAfterAmountImpactingChange();
9440
9441        _firePaymentAmountChangedEvent();
9442
9443    };
9444
9445    const _handleRecurringPaymentAmountChanged = function () {
9446
9447        //_logger.Log('payment amount chaned handler called');
9448
9449        //_refreshPaymentAmountSectionForFormUpdates();
9450        //
9451        //_updateFormAfterAmountImpactingChange();
9452
9453        _fireRecurringAmountChangedEvent();
9454
9455    };
9456
9457    let _lastCurrency_id = 0;
9458
9459    const _convertFormCurrency = function (fromCurrency_id, toCurrency_id) {
9460
9461        if (fromCurrency_id > 0 && toCurrency_id > 0 && fromCurrency_id != toCurrency_id) {
9462
9463            ////first update amount.
9464            //let transAmt = thisApi.Payment_XXX_Amount;
9465            //
9466            //if (transAmt > 0) {
9467            //
9468            //    let newAmt = performCurrencyConversion(fromCurrency_id, transAmt, toCurrency_id, currencyMap, 2);
9469            //
9470            //    if (newAmt != null) {
9471            //        let newAmtRounded = roundEndUserSuggestedDonationAmount(newAmt);
9472            //        thisApi.Payment_XXX_AmountField.SetValue(newAmtRounded);
9473            //    }
9474            //    else
9475            //        _logger.Log('currency conversion failed');
9476            //
9477            //}
9478
9479            for (let x = 0; x < thisApi.GenericAmountInputFields.length; x++) {
9480
9481                //first update amount.
9482                let transAmt = thisApi.GenericAmountInputFields[x].Value;
9483
9484                if (transAmt > 0) {
9485
9486                    let newAmt = performCurrencyConversion(fromCurrency_id, transAmt, toCurrency_id, currencyMap, 2);
9487
9488                    if (newAmt != null) {
9489                        let newAmtRounded = roundEndUserSuggestedDonationAmount(newAmt);
9490                        thisApi.GenericAmountInputFields[x].SetValue(newAmtRounded);
9491                    }
9492                    else
9493                        _logger.Log('currency conversion failed');
9494
9495                }
9496
9497            }
9498
9499            //if we have ask string, update them.
9500
9501            let newCurSym = _getPaymentCurrencySymbol();
9502
9503            let asks = _getAmountAskStringInputs();
9504
9505            for (let x = 0; x < asks.length; x++) {
9506
9507                let curAskAmt = asks[x].parentElement.tryGetDecimalAttribute('data-converted-amount');
9508
9509                if (curAskAmt <= 0)
9510                    curAskAmt = asks[x].value.toDecimalZeroIfInvalid();
9511
9512                let newAskAmt = performCurrencyConversion(fromCurrency_id, curAskAmt, toCurrency_id, currencyMap, 2);
9513                if (newAskAmt != null) {
9514
9515                    asks[x].parentElement.setAttribute('data-converted-amount', newAskAmt);
9516
9517                    let newAskAmtRounded = roundEndUserSuggestedDonationAmount(newAskAmt);
9518
9519                    asks[x].value = newAskAmtRounded;
9520
9521                    let askBlockElem = asks[x].closest('.ce-amountAskStringButtonContainers');
9522
9523                    let lbl = getLabelForField(asks[x], askBlockElem);
9524                    if (lbl != null) {
9525
9526                        let lastSymb = lbl.getAttribute('data-currency-symbol');
9527
9528                        if (!isBlank(lastSymb)) {
9529                            lbl.innerHTML = formatCurrency(newAskAmtRounded, false, newCurSym);
9530                            lbl.setAttribute('data-currency-symbol', newCurSym)
9531                        }
9532
9533                    }
9534
9535                }
9536                else
9537                    _logger.Log('currency conversion failed');
9538
9539            }
9540
9541        }
9542
9543    };
9544
9545    const _updateAllFieldCurrencyPrefixes = function () {
9546
9547        let sym = _getPaymentCurrencySymbol();
9548
9549        let allPrefixes = document.querySelectorAll('.ce-formFieldCurrencyPrefixes');
9550        for (let x = 0; x < allPrefixes.length; x++)             
9551            allPrefixes[x].innerHTML = sym;        
9552
9553    };
9554
9555    const _handleCurrencyChanged = function () {
9556
9557        let currency_id = thisApi.PaymentCurrencyField.Value;
9558
9559        _logger.Log('currency changed to ' + currency_id);
9560
9561        if (currency_id > 0) {
9562
9563            _convertFormCurrency(_lastCurrency_id, currency_id);
9564
9565            _lastCurrency_id = currency_id;
9566
9567        }
9568
9569        _updateAllFieldCurrencyPrefixes();
9570
9571        _refreshPaymentAmountSectionForFormUpdates();
9572
9573        _updateSubmitButtonText();
9574
9575        _firePaymentCurrencyChangedEvent();
9576
9577    };
9578
9579    const _handlePaymentMethodChanged = function () {
9580
9581        //hide fields that are not applicable
9582        _hideAllPaymentFields();
9583
9584        let payMethId = thisApi.PaymentMethodId;
9585
9586        _logger.Log('payment method changed to ' + payMethId);
9587
9588        _hideCoverFeesWidget();
9589
9590        switch (payMethId) {
9591            case PAYMENT_METHODS.CREDIT_CARD:
9592
9593                _showCoverFeesWidget();
9594
9595                let payWalletTypeId = _getPaymentWalletTypeId();
9596                //_logger.Log('wallet: ' + payWalletTypeId);
9597                switch (payWalletTypeId) {
9598                    case WALLET_TYPES.NONE:
9599
9600                        _tryShowField(thisApi.CreditCardTypeField);
9601                        _tryShowField(thisApi.CreditCardNumberField);
9602                        _tryShowField(thisApi.CreditCardHolderNameField);
9603                        _tryShowField(thisApi.CreditCardExpirationDateField);
9604                        _tryShowField(thisApi.CreditCardExpirationMonthField);
9605                        _tryShowField(thisApi.CreditCardExpirationYearField);
9606                        _tryShowField(thisApi.CreditCardSecurityCodeField);
9607
9608                        if (document.getElementById('togglePaymentMethodBillingInfoContainer') != null)
9609                            document.getElementById('togglePaymentMethodBillingInfoContainer').show();
9610
9611                        _handleToggleBillingInfoClicked();
9612
9613                        ////TODO: do these need to be show/hidden via billing info different?
9614                        //_tryShowField(thisApi.BillingAddressStreet1Field);
9615                        //_tryShowField(thisApi.BillingAddressStreet2Field);
9616                        //_tryShowField(thisApi.BillingAddressCityField);
9617                        //_tryShowField(thisApi.BillingAddressStateProvinceField);
9618                        //_tryShowField(thisApi.BillingAddressPostalCodeField);
9619                        //_tryShowField(thisApi.BillingAddressCountryField);
9620
9621                        thisApi.ShowSubmitButton();
9622
9623                        break;
9624                    case WALLET_TYPES.GOOGLE_PAY:
9625                        thisApi.GooglePayApi.ShowButton();
9626                        break;
9627                    case WALLET_TYPES.APPLE_PAY:
9628                        thisApi.ApplePayApi.ShowButton();
9629                        break;
9630                }                
9631
9632                break;
9633            case PAYMENT_METHODS.ACH:
9634
9635                _showCoverFeesWidget();
9636
9637                _tryShowField(thisApi.BankAccountNumberField);
9638                _tryShowField(thisApi.BankAccountTypeField);
9639                _tryShowField(thisApi.BankRoutingNumberField);
9640                _tryShowField(thisApi.BankAccountNameOnAccountField);
9641                _tryShowField(thisApi.ACHTransactionTypeField);
9642                _tryShowField(thisApi.BankNameField);                
9643
9644                thisApi.ShowSubmitButton();
9645
9646                break;
9647            case PAYMENT_METHODS.PAYPAL:
9648                _showCoverFeesWidget();
9649                thisApi.PayPalApi.ShowButton();
9650                break;
9651            case PAYMENT_METHODS.SOFT_CREDIT:
9652                _showCoverFeesWidget();
9653                thisApi.DafPayApi.ShowButton();
9654                break;
9655            case PAYMENT_METHODS.IN_KIND:
9656
9657                _refreshInKindFieldsByTypeSelected();
9658
9659                thisApi.ShowSubmitButton();
9660
9661                break;
9662            case PAYMENT_METHODS.NONE:
9663            case PAYMENT_METHODS.CHECK:
9664            case PAYMENT_METHODS.CASH:
9665                thisApi.ShowSubmitButton();
9666                break;
9667            case PAYMENT_METHODS.ONECLICK_DONATION:
9668
9669                _showCoverFeesWidget();
9670
9671                //LEGACY IDEA
9672                _setOneClickDefaultState();
9673
9674                thisApi.ShowSubmitButton();                
9675
9676                break;
9677        }
9678
9679        _updateSubmitButtonText();
9680
9681        _firePaymentMethodChangedEvent();
9682
9683    };
9684
9685    //Core Processing APIs (validation)
9686
9687    const _validateFieldById = function (field_id) {
9688
9689        let fld = _getFieldById(field_id);
9690        if (fld != null)
9691            fld.Validate(); //_validate_XXX_Field(fld);
9692
9693    };
9694
9695    const _validateFieldElementRelative = function (fieldOrChildElement) {
9696
9697        if (fieldOrChildElement != null) {
9698
9699            let field_id = 0;
9700
9701            let actualFieldElem = fieldOrChildElement.firstParentOrSelfWithAttribute('data-field-id');
9702            if (actualFieldElem != null)
9703                field_id = _getFieldElementFieldId(actualFieldElem);
9704
9705            if (field_id > 0)
9706                _validateFieldById(field_id);
9707
9708        }
9709
9710    };
9711
9712    thisApi.ValidateSectionByPosition = function (sectionIndexPosition) {
9713
9714        let retVal = true;
9715
9716        const sec = _getSectionByIndex(sectionIndexPosition);        
9717        if (sec != null)
9718            retVal = sec.Validate();
9719
9720        return retVal;
9721
9722    };
9723
9724    const _validateFormLevelValidationRules = function () {
9725
9726        const retVal = { validationResult: true, validationMessage: '' };
9727
9728        //validate minimum charge amount
9729        const chargeAmt = thisApi.GetTransactionAmount();
9730        if (retVal.validationResult && WebFormContext.Form.MinimumChargeAmount > chargeAmt) {
9731            retVal.validationResult = false;
9732            retVal.validationMessage = WebFormContext.Form.MinimumChargeAmountMessage;
9733        }
9734
9735        //validate volunteer post selection
9736        if (retVal.validationResult && thisApi.VolunteerPostsField != null && thisApi.SelectedVolunteerPostIds.length <= 0) {
9737            retVal.validationResult = false;
9738            retVal.validationMessage = 'Please select one or more volunteer posts';
9739        }
9740
9741
9742        //validate opt-in list selection - if opt-in list options are present, one must be chosen
9743        if (retVal.validationResult && thisApi.EmailOptInListsField != null && thisApi.EmailOptInListsField.HasOptions && thisApi.SelectedEmailOptInListIds.length <= 0) {
9744            retVal.validationResult = false;
9745            retVal.validationMessage = 'Please select an email opt-in option';
9746        }
9747
9748        //validate volunteer time selection
9749        if (retVal.validationResult && thisApi.VolunteerTimeEntryFields != null && thisApi.VolunteerTimeEntryFields.length > 0) {
9750
9751            let volunteerPostTimeEntryRows = document.querySelectorAll('.ce-volunteerPostTimeEntryFieldRows');
9752
9753            let hasValidTimeEntry = false;
9754            let hasPartialTimeEntry = false;
9755
9756            for (let x = 0; x < volunteerPostTimeEntryRows.length; x++) {
9757
9758                let rowElem = volunteerPostTimeEntryRows[x];
9759
9760                let startDate = rowElem.querySelector('.ce-volunteerPostTimeEntryStartDates').getValue().toDotNetAjaxDateTimeOrNull();
9761                let endDate = rowElem.querySelector('.ce-volunteerPostTimeEntryEndDates').getValue().toDotNetAjaxDateTimeOrNull();
9762
9763                if (startDate != null && endDate != null)
9764                    hasValidTimeEntry = true;
9765                else if (startDate != null)
9766                    hasPartialTimeEntry = true;
9767                else if (endDate != null)
9768                    hasPartialTimeEntry = true;
9769
9770            }
9771
9772            if (!hasValidTimeEntry || hasPartialTimeEntry) {
9773
9774                retVal.validationResult = false;
9775                retVal.validationMessage = 'Please enter volunteer post start and end times';
9776
9777            }
9778
9779        }
9780
9781        //validate if ticket quantities are on the form and the charge amount is $0, then ensure a t
9781icket was selected ($0 tickets)
9782        if (retVal.validationResult && thisApi.TicketQuantityFields.length > 0 && chargeAmt <= 0) {
9783
9784            let ticketsSelected = false;
9785
9786            for (let x = 0; x < thisApi.TicketQuantityFields.length; x++)
9787                if (thisApi.TicketQuantityFields[x].DomElement != null) {
9788
9789                    if (thisApi.TicketQuantityFields[x].Value > 0) {
9790                        ticketsSelected = true;
9791                        break;
9792                    }
9793
9794                }
9795
9796            if (!ticketsSelected) {
9797                retVal.validationResult = false;
9798                retVal.validationMessage = 'Please select at least one ticket or set a donation amount.';
9799            }
9800        }
9801
9802        //validate ticket quantities don't exceed the max allowed (plus/minus buttons enforce this, but a manually typed value can bypass them)
9803        for (let x = 0; retVal.validationResult && x < thisApi.TicketQuantityFields.length; x++)
9804            if (thisApi.TicketQuantityFields[x].DomElement != null) {
9805
9806                const maxAllowed = thisApi.TicketQuantityFields[x].DomElement.tryGetIntegerAttribute('data-max-allowed');
9807
9808                if (maxAllowed > 0 && thisApi.TicketQuantityFields[x].Value > maxAllowed) {
9809                    retVal.validationResult = false;
9810                    retVal.validationMessage = 'Please select ' + maxAllowed + ' or fewer of ' + getFieldLabelText(thisApi.TicketQuantityFields[x].LabelDomElement);
9811                }
9812
9813            }
9814
9815        //validate tickets that are sold out or outside their configured sale start/end date window aren't being purchased
9816        for (let x = 0; retVal.validationResult && x < thisApi.TicketQuantityFields.length; x++)
9817            if (thisApi.TicketQuantityFields[x].DomElement != null && !_isTicketPurchaseEnabled(thisApi.TicketQuantityFields[x]) && thisApi.TicketQuantityFields[x].Value > 0) {
9818                retVal.validationResult = false;
9819                retVal.validationMessage = getFieldLabelText(thisApi.TicketQuantityFields[x].LabelDomElement) + ' is not currently available for purchase';
9820            }
9821
9822        //products
9823        if (typeof WebFormContext.Form.ProductWidgetApis != 'undefined' && WebFormContext.Form.ProductWidgetApis.length > 0) {
9824
9825            for (let x = 0;x < WebFormContext.Form.ProductWidgetApis.length;x++) {
9826
9827                if (WebFormContext.Form.ProductWidgetApis[x].SelectionDetails.Quantity > 0){
9828
9829                    const prodValResult = WebFormContext.Form.ProductWidgetApis[x].Validate();
9830
9831                    console.log('prodValResult: ' + prodValResult);
9832
9833                    if (!prodValResult) {
9834                        retVal.validationResult = false;
9835                        retVal.validationMessage = 'Product selection not valid';    
9836                        break;
9837                    }
9838
9839                }
9840
9841            }
9842
9843        }
9844
9845        return retVal;
9846
9847    };
9848
9849    let _formValidationSucceeded = true;
9850    thisApi.FormValidationMessage = '';
9851
9852    //Form Validation
9853    thisApi.Validate = function () {
9854
9855        _formValidationSucceeded = true;
9856        thisApi.FormValidationMessage = '';
9857
9858        //TODO: Add prevalidation Event
9859
9860        _logger.Log('validate ' + _allSectionApis.length + ' sections');
9861
9862        for (let x = 0; x < _allSectionApis.length; x++) {
9863
9864            let secValidated = _allSectionApis[x].Validate();
9865            if (!secValidated)
9866                _formValidationSucceeded = false;
9867
9868        }
9869
9870        if (_membershipAdditionalContactApi != null) { 
9871
9872            let memSubMemValidated = _membershipAdditionalContactApi.ValidateRows();
9873            if(!memSubMemValidated)
9874                _formValidationSucceeded = memSubMemValidated
9875
9876        }        
9877
9878        //field level validation failed, show generic message
9879        if (!_formValidationSucceeded)
9880            thisApi.FormValidationMessage = 'Check all fields marked in red below';
9881
9882        //check form level validation rules (ie min charge amount)
9883        let formLevelValidated = _validateFormLevelValidationRules();
9884
9885        if (!formLevelValidated.validationResult) {
9886
9887            _formValidationSucceeded = formLevelValidated.validationResult;
9888
9889            if (!isBlank(thisApi.FormValidationMessage))
9890                thisApi.FormValidationMessage += '<br />';
9891            
9892            thisApi.FormValidationMessage += formLevelValidated.validationMessage;
9893
9894        }
9895
9896        //Should we have a valid and invalid event or just one saying validation has run?
9897        _fireFormValidatedEvent();
9898
9899        return _formValidationSucceeded;
9900
9901    };
9902
9903    //Core Processing Field Value Getters & Setters (simple one-to-one fields)
9904
9905    const _getPaymentCurrencySymbol = function () { return App.Currencies.GetCurrencySymbolByIdOrUSD(_tryGetIdFieldValueOrZero(thisApi.PaymentCurrencyField)); };
9906
9907    const _getPaymentWalletTypeId = function () {
9908
9909        let retVal = WALLET_TYPES.NONE;
9910
9911        if (thisApi.PaymentMethodField != null && thisApi.PaymentMethodField.SelectedOptionDomElement != null)
9912            retVal = thisApi.PaymentMethodField.SelectedOptionDomElement.tryGetIntegerAttribute('data-wallet-type-id');
9913
9914        return retVal;
9915
9916    };
9917
9918    thisApi.GetPaymentCurrencySymbol = function () { return _getPaymentCurrencySymbol(); };
9919
9920    thisApi.GetTicketQuantities = function () {
9921
9922        let retVal = [];
9923
9924        for (let x = 0; x < thisApi.TicketQuantityFields.length; x++)
9925            if (thisApi.TicketQuantityFields[x].DomElement != null)
9926                retVal.push({ "Ticket_Id": thisApi.TicketQuantityFields[x].DomElement.tryGetIntegerAttribute('data-ticket-id'), "Quantity": thisApi.TicketQuantityFields[x].Value });
9927
9928        return retVal;
9929
9930    };
9931
9932    thisApi.GetTicketCount = function () {
9933
9934        let retVal = 0;
9935
9936        for (let x = 0; x < thisApi.TicketQuantityFields.length; x++)
9937            if (thisApi.TicketQuantityFields[x].DomElement != null)
9938                retVal += thisApi.TicketQuantityFields[x].Value;
9939
9940        return retVal;
9941
9942    };
9943
9944    thisApi.GetTicketGLCodeMapping = function () {
9945
9946        let retVal = [];
9947
9948        for (let x = 0; x < thisApi.TicketQuantityFields.length; x++)
9949            if (thisApi.TicketQuantityFields[x].DomElement != null && thisApi.TicketQuantityFields[x].Value > 0 && thisApi.TicketQuantityFields[x].DomElement.tryGetIntegerAttribute('data-ticket-glCode-id') > 0) {
9950                let glCodeId = thisApi.TicketQuantityFields[x].DomElement.tryGetIntegerAttribute('data-ticket-glCode-id');
9951
9952                let index = retVal.findIndex(e => e.Id === glCodeId);
9953
9954                if (index > -1)
9955                    retVal[index].Amount += (thisApi.TicketQuantityFields[x].Value * thisApi.TicketQuantityFields[x].DomElement.tryGetDecimalAttribute('data-ticket-price'));
9956                else
9957                    retVal.push({ "Id": glCodeId, "Amount": (thisApi.TicketQuantityFields[x].Value * thisApi.TicketQuantityFields[x].DomElement.tryGetDecimalAttribute('data-ticket-price')) });
9958
9959            }
9960
9961        return retVal;
9962    }
9963
9964    thisApi.GetRegistrationAmount = function () {
9965
9966        let retVal = 0;
9967
9968        for (let x = 0; x < thisApi.TicketQuantityFields.length; x++)
9969            if (thisApi.TicketQuantityFields[x].DomElement != null && thisApi.TicketQuantityFields[x].Value > 0)
9970                retVal += (thisApi.TicketQuantityFields[x].Value * thisApi.TicketQuantityFields[x].DomElement.tryGetDecimalAttribute('data-ticket-price'));
9971
9972        return retVal;
9973
9974    };
9975
9976    thisApi.GetProductsAmount = function(){
9977    
9978        let retVal = 0.00;
9979
9980        if (typeof WebFormContext.Form.ProductWidgetApis != 'undefined' && WebFormContext.Form.ProductWidgetApis.length > 0) {
9981
9982            for (let x = 0;x < WebFormContext.Form.ProductWidgetApis.length;x++)
9983                if (WebFormContext.Form.ProductWidgetApis[x].SelectionDetails.Quantity > 0) 
9984                    retVal += (WebFormContext.Form.ProductWidgetApis[x].SelectionDetails.Quantity * WebFormContext.Form.ProductWidgetApis[x].SelectionDetails.UnitPrice);
9985
9986        }
9987
9988        return retVal;
9989
9990    };
9991
9992    thisApi.GetRegistrationTaxDeductibleAmount = function () {
9993
9994        let retVal = 0;
9995
9996        for (let x = 0; x < thisApi.TicketQuantityFields.length; x++)
9997            if (thisApi.TicketQuantityFields[x].DomElement != null && thisApi.TicketQuantityFields[x].Value > 0)
9998                retVal += (thisApi.TicketQuantityFields[x].Value * thisApi.TicketQuantityFields[x].DomElement.tryGetDecimalAttribute('data-ticket-tax-deductible-amount'));
9999
10000        return retVal;
10001
10002    };
10003
10004    thisApi.GetPreDiscountTaxDeductibleAmount = function () {
10005
10006        return (thisApi.GenericTaxDeductibleAmountInputsAmount + thisApi.GetRegistrationTaxDeductibleAmount() + _get
10006SelectedMembershipTaxDeductibleAmount());
10007
10008    };
10009
10010    thisApi.GetPreDiscountAmount = function () {
10011
10012        return (thisApi.GenericAmountInputsAmount + thisApi.GetRegistrationAmount() + thisApi.GetProductsAmount() + _getSelectedMembershipRate());
10013
10014    };
10015
10016    thisApi.GetPromoCodeApplicableTotalAmount = function () {
10017
10018        let regAmt = thisApi.GetRegistrationAmount();
10019        let memAmt = _getSelectedMembershipRate();
10020
10021        let retVal = (regAmt + memAmt);
10022
10023        //todo: do we need to add product amount to this?
10024        //we need special business rules to ensure amount is not a donation - we do not want to discount a donation
10025        //for now we will be somewhat restrictive and 
10026        if (WebFormContext.Form.Type.Id != FORM_TYPES.Donation && WebFormContext.Form.Type.Id != FORM_TYPES.Event_Registration && WebFormContext.Form.Type.Id != FORM_TYPES.Membership && regAmt <= 0 && memAmt <= 0)
10027            retVal += thisApi.GenericAmountInputsAmount;
10028
10029        return retVal;
10030
10031    };
10032
10033    thisApi.GetDiscountAmount = function () {
10034
10035        let retVal = 0.00;
10036
10037        if (thisApi.PromoCodeFieldApi != null)
10038            retVal = thisApi.PromoCodeFieldApi.GetDiscountAmount();
10039
10040        return retVal;
10041
10042    };
10043
10044    thisApi.GetTicketsDiscountAmount = function () {
10045
10046        let retVal = 0.00;
10047
10048        if (thisApi.PromoCodeFieldApi != null)
10049            retVal = thisApi.PromoCodeFieldApi.GetTicketsDiscountAmount();
10050
10051        return retVal;
10052
10053    };
10054
10055    thisApi.GetNonTicketDiscountAmount = function () {
10056
10057        let retVal = 0.00;
10058
10059        if (thisApi.PromoCodeFieldApi != null)
10060            retVal = thisApi.PromoCodeFieldApi.GetNonTicketDiscountAmount();
10061
10062        return retVal;
10063
10064    };
10065
10066    thisApi.GetDiscountedAmount = function () {
10067         
10068        return (thisApi.GetPreDiscountAmount() - thisApi.GetDiscountAmount());
10069
10070    };
10071
10072    thisApi.GetTransactionAmount = function () {
10073
10074        let fees = _calculateFees();
10075
10076        return (thisApi.GetDiscountedAmount() + fees);
10077
10078    };
10079
10080    thisApi.GetRecurringTransactionAmount = function () {
10081
10082        let retVal = 0.00;
10083
10084        if (thisApi.RecurringPaymentFrequencyId > 0) {
10085            let fees = _calculateFees();
10086
10087            retVal = thisApi.RecurringPaymentAmount + fees;
10088        }
10089
10090        return retVal;
10091    }
10092
10093    thisApi.GetTransactionTaxDeductibleAmount = function () {
10094
10095        let tdAmt = thisApi.GetPreDiscountTaxDeductibleAmount();
10096
10097        let retVal = (tdAmt - thisApi.GetDiscountAmount());
10098
10099        if (retVal < 0)
10100            retVal = 0;
10101
10102        return retVal;
10103
10104    };
10105
10106    //CORE Processing - Form View Controllers
10107
10108    const _hideAllSections = function () {
10109
10110        for (let x = 0; x < _allSectionApis.length; x++)
10111            _allSectionApis[x].Hide();
10112
10113    };
10114
10115    const _updateFieldAndSectionVisibilityStateForView = function () {
10116
10117        switch (WebFormContext.RenderedViewNumber) {
10118            case FORM_RENDER_VIEWS.Unknown:
10119            case FORM_RENDER_VIEWS.LandingPage:
10120
10121                for (let x = 0; x < _allFieldApis.length; x++)
10122                    if (_allFieldApis[x].Configuration.ShowOnLandingPage) {
10123                        _logger.Log('show field ' + _allFieldApis[x].Configuration.DomId + ' based on landing page visiblity');
10124                        _allFieldApis[x].Show();
10125                    }
10126                    else {
10127                        _logger.Log('hide field ' + _allFieldApis[x].Configuration.DomId + ' based on landing page visiblity');
10128                        _allFieldApis[x].Hide();
10129                    }
10130
10131                for (let x = 0; x < _allSectionApis.length; x++)
10132                    if (_allSectionApis[x].Configuration.ShowOnLandingPage) {
10133                        _logger.Log('show section ' + _allSectionApis[x].Configuration.Id + ' based on landing page visiblity');
10134                        _allSectionApis[x].Show();
10135                    }
10136                    else {
10137                        _logger.Log('hide section ' + _allSectionApis[x].Configuration.Id + ' based on landing page visiblity');
10138                        _allSectionApis[x].Hide();
10139                    }
10140
10141                break;
10142            case FORM_RENDER_VIEWS.ConfirmationPage:
10143
10144                for (let x = 0; x < _allFieldApis.length; x++)
10145                    if (_allFieldApis[x].Configuration.ShowOnConfirmationPage)
10146                        _allFieldApis[x].Show();
10147                    else
10148                        _allFieldApis[x].Hide();
10149
10150                for (let x = 0; x < _allSectionApis.length; x++)
10151                    if (_allSectionApis[x].Configuration.ShowOnConfirmationPage)
10152                        _allSectionApis[x].Show();
10153                    else
10154                        _allSectionApis[x].Hide();
10155
10156                break;
10157            case FORM_RENDER_VIEWS.ErrorPage:
10158
10159                for (let x = 0; x < _allFieldApis.length; x++)
10160                    if (_allFieldApis[x].Configuration.ShowOnErrorPage)
10161                        _allFieldApis[x].Show();
10162                    else
10163                        _allFieldApis[x].Hide();
10164
10165                for (let x = 0; x < _allSectionApis.length; x++)
10166                    if (_allSectionApis[x].Configuration.ShowOnErrorPage)
10167                        _allSectionApis[x].Show();
10168                    else
10169                        _allSectionApis[x].Hide();
10170
10171                break;
10172            case FORM_RENDER_VIEWS.SuccessPage:
10173
10174                for (let x = 0; x < _allFieldApis.length; x++)
10175                    if (_allFieldApis[x].Configuration.ShowOnSuccessPage)
10176                        _allFieldApis[x].Show();
10177                    else
10178                        _allFieldApis[x].Hide();
10179
10180                for (let x = 0; x < _allSectionApis.length; x++)
10181                    if (_allSectionApis[x].Configuration.ShowOnSuccessPage)
10182                        _allSectionApis[x].Show();
10183                    else
10184                        _allSectionApis[x].Hide();
10185
10186                break;
10187        }
10188
10189    };
10190
10191    const _setFormViewState = function (renderedViewNumber) {
10192
10193        thisApi.FormElement.removeClassName('ce-landingPageViews');
10194        thisApi.FormElement.removeClassName('ce-confirmationPageViews');
10195        thisApi.FormElement.removeClassName('ce-errorPageViews');
10196        thisApi.FormElement.removeClassName('ce-successPageViews');
10197
10198        WebFormContext.RenderedViewNumber = renderedViewNumber; // FORM_RENDER_VIEWS.LandingPage;
10199
10200        switch (WebFormContext.RenderedViewNumber) {
10201            case FORM_RENDER_VIEWS.LandingPage:
10202                WebFormContext.RenderedViewName = 'Landing Page';
10203                thisApi.FormElement.addClassName('ce-landingPageViews');
10204                break;
10205            case FORM_RENDER_VIEWS.ConfirmationPage:
10206                WebFormContext.RenderedViewName = 'Confirmation Page';
10207                thisApi.FormElement.addClassName('ce-confirmationPageViews');
10208                break;
10209            case FORM_RENDER_VIEWS.ErrorPage:
10210                WebFormContext.RenderedViewName = 'Error Page';
10211                thisApi.FormElement.addClassName('ce-errorPageViews');
10212                break;
10213            case FORM_RENDER_VIEWS.SuccessPage:
10214                WebFormContext.RenderedViewName = 'Success Page';
10215                thisApi.FormElement.addClassName('ce-successPageViews');
10216                break;
10217        }
10218
10219
10220    };
10221     
10222    const _clearHeaderMessageContent = function () {
10223        thisApi.HeaderMessagesContainerElement.removeClassName('error');
10224        thisApi.HeaderMessagesContainerElement.removeClassName('success');
10225        thisApi.HeaderMessagesContainerElement.removeClassName('warning');
10226        _setHeaderMessageContent();
10227    };
10228    
10229    const _setHeaderMessageContent = function (title, message) {
10230        
10231        thisApi.HeaderMessagesContainerElement.innerHTML = '';
10232
10233        if (!isBlank(title) || !isBlank(message)) {
10234
10235            thisApi.HeaderMessagesContainerElement.show();
10236
10237            if (!isBlank(title))
10238                thisApi.HeaderMessagesContainerElement.addHtml(title, 'headerDynamicMessageTitle');
10239
10240            if (!isBlank(message))
10241                thisApi.HeaderMessagesContainerElement.addHtml(message, 'headerDynamicMessageContent');
10242
10243            scrollToTopOfWindow();
10244
10245        }
10246        else
10247            thisApi.HeaderMessagesContainerElement.vanish();
10248
10249    };
10250
10251    const _setHeaderErrorContent = function (title, message) {
10252        thisApi.HeaderMessagesContainerElement.addClassName('error');
10253        _setHeaderMessageContent(title, message);
10254    }
10255
10256    const _hideAllPageContentElements = function () {
10257
10258        if (thisApi.TitleContainerElement != null)
10259            thisApi.TitleContainerElement.vanish();
10260
10261        if (thisApi.HeaderContainerElement != null)
10262            thisApi.HeaderContainerElement.vanish();
10263
10264        if (thisApi.FooterContainerElement != null)
10265            thisApi.FooterContainerElement.vanish();
10266
10267    };
10268
10269    const _setPageContent = function (title, headerContent, footerContent) {
10270
10271        if (thisApi.TitleContainerElement != null)
10272            thisApi.TitleContainerElement.innerHTML = title;
10273
10274        if (thisApi.HeaderContainerElement != null)
10275            thisApi.HeaderContainerElement.innerHTML = headerContent;
10276
10277        if (thisApi.FooterContainerElement != null)
10278            thisApi.FooterContainerElement.innerHTML = footerContent;
10279
10280    };
10281
10282    const _setDefaultView = function () {
10283
10284        _setFormViewState(FORM_RENDER_VIEWS.LandingPage);
10285        
10286        _clearHeaderMessageContent();        
10287
10288        //moved to server side for token parsing
10289        //_setPageContent(WebFormContext.Form.DefaultViewContent.Title, WebFormContext.Form.DefaultViewContent.HeaderContent, WebFormContext.Form.DefaultViewContent.FooterContent); //Landing Page Content is Pre-Parsed
10290
10291        _updateFieldAndSectionVisibilityStateForView();
10292
10293        _hideStartOverButton();
10294        _hidePrintButton();
10295
10296    };
10297
10298    //TBD if we implement
10299    //const _setConfirmView = function (title, headerContent, footerContent) 
10300
10301    const _setErrorView = function (error, errorMessage, pageTitle, headerContent, footerContent) {
10302
10303        _setFormViewState(FORM_RENDER_VIEWS.ErrorPage);
10304
10305        _setHeaderMessageContent(error, errorMessage);
10306
10307        //let's not change content for now, this migth be a deprecated 
10307idea _setPageContent(pageTitle, headerContent, footerContent)
10308
10309    };
10310
10311    const _getStartOverButton = function () { return document.getElementById('webFormStartOverButton'); };
10312
10313    const _showStartOverButton = function () { _addStartOverButton(); document.getElementById('webFormStartOverButton').show(); };
10314    const _hideStartOverButton = function () { if (document.getElementById('webFormStartOverButton') != null) document.getElementById('webFormStartOverButton').vanish(); };
10315
10316    const _addStartOverButton = function () {
10317
10318        let sob = _getStartOverButton();
10319        if (sob == null)
10320            thisApi.AddButton('webFormStartOverButton', 'Start Over', function () { reloadCurrentUrl(); });
10321
10322    };
10323
10324    const _getPrintButton = function () { return document.getElementById('webFormPrintButton'); };
10325
10326    const _showPrintButton = function () { _addPrintButton(); document.getElementById('webFormPrintButton').show(); };
10327    const _hidePrintButton = function () { if (document.getElementById('webFormPrintButton') != null) document.getElementById('webFormPrintButton').vanish(); };
10328
10329    const _addPrintButton = function () {
10330
10331        let pb = _getPrintButton();
10332        if (pb == null)
10333            thisApi.AddButton('webFormPrintButton', WebFormContext.Form.PrintButtonText, function () { printThisPage(); });
10334
10335    };
10336
10337    //TODO: we may not need this (might need to be returns from server as part of process transaction response)
10338    const _setSuccessView = function (title, successMessage, pageTitle, headerContent, footerContent) {
10339
10340        _setFormViewState(FORM_RENDER_VIEWS.SuccessPage);
10341
10342        //this is the system message
10343        _setHeaderMessageContent(title, successMessage);
10344
10345        //this is the configured page content
10346        _setPageContent(pageTitle, headerContent, footerContent)
10347
10348        _hideAllSections(); //new version hides fields on success // _updateFieldAndSectionVisibilityStateForView();
10349
10350        thisApi.HideSubmitButton();
10351
10352        _showStartOverButton();
10353
10354        if (WebFormContext.Form.ShowPrintButtonOnSuccess)
10355            _showPrintButton();
10356
10357    };
10358
10359    //Third-Party Payment Method Handlers
10360
10361    const _handlePayPalTokenReceived = function (result) {
10362
10363        //_logger.Log('paypal token received: ' + JSON.stringify(result));
10364
10365        thisApi.PaymentMethodField.SetValue(PAYMENT_METHODS.PAYPAL);
10366
10367        //_walletTypeId = WALLET_TYPES.NONE;
10368
10369        _payPalTokenId = result.PayPalToken;
10370        _payPalAmountChargedResponse = result.ChargeAmount;
10371        
10372    };
10373
10374    const _handlePaypalPaymentAuthorized = function (result) {
10375
10376        //_logger.Log('paypal payment authorized: ' + JSON.stringify(result));
10377
10378        _payPalGatewayPayerId = result.PayPalGatewayPayerId;
10379
10380        thisApi.ProcessTransaction();
10381
10382    };
10383
10384    const _handleApplePayPaymentAuthorized = function (result) {
10385
10386        //_logger.Log('ApplePay payment authorized call back: ' + result);
10387
10388        //Pamyent method is already set to credit card at this point. This changes the payment method selection causing credit card field validation to occur
10389        //thisApi.PaymentMethodField.SetValue(PAYMENT_METHODS.CREDIT_CARD);
10390
10391        //_walletTypeId = WALLET_TYPES.APPLE_PAY;
10392
10393        _walletToken = result.ApplePayToken;
10394
10395        _walletCreditCardType_id = result.CardType_Id;
10396        _walletEciIndicator = result.ApplePayTokenEciIndicator; //required for first transaction
10397
10398        _tokenExpirationMonth = result.ExpirationMonth;
10399        _tokenExpirationYear = result.ExpirationYear;
10400
10401        _tokenCryptogram = result.ApplePayCryptogram;
10402
10403        thisApi.ProcessTransaction();
10404
10405    }; 
10406
10407    const _tryHideApplePayButton = function () {
10408        if (thisApi.ApplePayApi != null) thisApi.ApplePayApi.HideButton();
10409    };
10410
10411    const _tryHideGooglePayButton = function () {
10412        if (thisApi.GooglePayApi != null) thisApi.GooglePayApi.HideButton();
10413    };
10414
10415    thisApi.HandleGooglePayScriptLoaded = function () {
10416
10417        _logger.Log('google pay script loaded (handler started)');
10418
10419        //_logger.Log('initialize googlepay API (' + (typeof thisApi.GooglePayApi) + ')');
10420        if (thisApi.GooglePayApi != null && typeof thisApi.GooglePayApi != 'undefined' && thisApi.GooglePayApi.GooglePayEnabled) {
10421            _logger.Log('Initialize googlepay native button');
10422            thisApi.GooglePayApi.RegisterPaymentAuthorizedCallBack(_handleGooglePayPaymentAuthorized);
10423            thisApi.GooglePayApi.Initialize();
10424        }
10425
10426        _hideGooglePayOptionIfSdkNotLoaded();
10427
10428        //if (thisApi.PaymentMethodField == null)
10429        //    thisApi.GooglePayApi.HideButton();
10430
10431    };
10432
10433    const _handleGooglePayPaymentAuthorized = function (result) {
10434
10435        //_logger.Log('googlepay wallet token decrypted successfully');
10436
10437        //Pamyent method is already set to credit card at this point. This changes the payment method selection causing credit card field validation to occur
10438        //thisApi.PaymentMethodField.SetValue(PAYMENT_METHODS.CREDIT_CARD);
10439
10440        //_walletTypeId = WALLET_TYPES.GOOG
10440LE_PAY;
10441
10442        _walletToken = result.paymentMethodDetails.pan;
10443
10444        _walletCreditCardType_id = 0;
10445        _walletEciIndicator = ''; //required for first transaction
10446
10447        _tokenExpirationMonth = result.paymentMethodDetails.expirationMonth;
10448        _tokenExpirationYear = result.paymentMethodDetails.expirationYear.toString().substr(-2);
10449
10450        _tokenCryptogram = '';
10451
10452        thisApi.ProcessTransaction();
10453
10454    };
10455
10456    const _handleDafPayButtonClicked = function (result) {
10457
10458        //_logger.Log('capture DAFpay button clicked event');
10459
10460        _dafPayWorkflowSessionId = ''; //always clear values at start
10461
10462    };
10463
10464    const _handleDafPayPaymentAuthorized = function (result) {
10465
10466        //_logger.Log('DAFpay authorization call back: ' + JSON.stringify(result));
10467
10468        if (!isBlank(result.DafPayResponse.workflowSessionId) && result.DafPayResponse.grantIntent != null && result.DafPayResponse.grantIntent.amount > 0) {
10469
10470            thisApi.PaymentMethodField.SetValue(PAYMENT_METHODS.SOFT_CREDIT);
10471
10472            //_walletTypeId = WALLET_TYPES.NONE;
10473
10474            let formattedDpAmount = Math.round(convertCentsToDollars(result.DafPayResponse.grantIntent.amount));
10475
10476            //overwrite special for DafPay
10477            thisApi.FirstGenericAmountInputField.SetValue(formattedDpAmount);
10478
10479            _dafPayWorkflowSessionId = result.DafPayResponse.workflowSessionId;
10480
10481            thisApi.ProcessTransaction();
10482
10483        }
10484
10485        //todo: reimplemnet for forms
10486        //else
10487        //    showErrorStep('Unable to perform DAFpay check out.  Please try another form of payment.', JSON.stringify(cbPresultaram
10487s));  
10488
10489    };
10490
10491    const _handleDafPayPaymentAuthorizationFailed = function (result) {
10492
10493        //_logger.Log('web form capture DAFpay failed call back: ' + JSON.stringify(result));
10494
10495        //todo: reimplemnet for forms
10496        //showErrorStep('Unable to perform DAFpay check out.  Please try another form of payment.', JSON.stringify(result));
10497
10498    };
10499
10500    const _hideAllContactAddressFields = function () {
10501
10502        for (let x = 0; x < _allFieldApis.length; x++)
10503            if (_allFieldApis[x].IsContactAddressField)
10504                _allFieldApis[x].Hide(false);
10505
10506        let showDetLink = document.getElementById('contactAddressSingleLineEntryRowShowDetailsLink');
10507        if (showDetLink != null) { 
10508
10509            showDetLink.setAttribute('title', 'edit details');
10510
10511            let icon = showDetLink.querySelector('.ce-formFieldEditButtonIcons');
10512            if (icon != null) {
10513
10514                if (icon.hasClassName('fa')) {
10515                    icon.removeClassName('fa-close');
10516                    icon.addClassName('fa-edit');
10517                }
10518                else
10519                    icon.innerText = 'add unit #';
10520
10521            }
10522
10523            //showDetLink.innerText = 'edit'; //todo: future config to allow text versus icon - TBD
10524
10525        }
10526         
10527    };
10528
10529    const _showAllContactAddressFields = function () {
10530
10531        for (let x = 0; x < _allFieldApis.length; x++)
10532            if (_allFieldApis[x].IsContactAddressField)
10533                _allFieldApis[x].Show();
10534
10535        let showDetLink = document.getElementById('contactAddressSingleLineEntryRowShowDetailsLink');
10536        if (showDetLink != null) { 
10537
10538            showDetLink.setAttribute('title', 'done editing');            
10539
10540            let icon = showDetLink.querySelector('.ce-formFieldEditButtonIcons');
10541            if (icon != null) {
10542
10543                if (icon.hasClassName('fa')) {
10544                    icon.removeClassName('fa-edit');
10545                    icon.addClassName('fa-close');
10546                }
10547                else
10548                    icon.innerText = 'done';
10549                
10550            }
10551
10552            //showDetLink.innerText = 'done'; //todo: future config to allow text versus icon - TBD
10553
10554        }
10555
10556    };
10557
10558    const _handleContactSingleLineAddressShowDetailsClick = function () {
10559         
10560        let showDetLink = document.getElementById('contactAddressSingleLineEntryRowShowDetailsLink');
10561        if (showDetLink != null) {
10562
10563            let icon = showDetLink.querySelector('.ce-formFieldEditButtonIcons');
10564            if (icon != null) {
10565                 
10566                if (icon.innerText.containsValue('done') || icon.hasClassName('fa-close'))
10567                    _hideAllContactAddressFields();                    
10568                else
10569                    _showAllContactAddressFields();                    
10570
10571            }
10572
10573            //todo: future config to allow text versus icon - TBD
10574            //if (showDetLink.innerText == 'edit')
10575            //    _showAllContactAddressFields();
10576            //else
10577            //    _hideAllContactAddressFields();
10578
10579        }
10580
10581    };
10582
10583    const _handleContactSingleLineAddressIntegrationFailure = function () {
10584
10585        let showDetLink = document.getElementById('contactAddressSingleLineEntryRow');
10586        if (showDetLink != null) {
10587
10588            showDetLink.vanish();
10589
10590            _showAllContactAddressFields(); 
10591             
10592        }
10593
10594    };
10595
10596    const _handleConactSingleLineAddressSelected = function (result) {
10597
10598        _logger.Log('contact single line address selected');
10599
10600        let selectedPlace = result.Result;
10601
10602        let street1Formatted = parseGoogleAddressLine1Short(selectedPlace);
10603        let city = parseGoogleCityShortName(selectedPlace);
10604        let state = parseGoogleStateShortName(selectedPlace);
10605        let zip = parseGooglePostalCodeLongName(selectedPlace);
10606        let country = parseGoogleCountryShortName(selectedPlace);
10607
10608        _logger.Log('country: ' + country);
10609
10610        let country_id = App.Countries.GetCountryIdByCodeOrAliasOrUsa(country);
10611        _logger.Log('country_id: ' + country_id);
10612
10613        _logger.Log('street1Formatted: ' + street1Formatted);
10614        _logger.Log('city: ' + city);
10615        _logger.Log('state: ' + state);
10616
10617        let state_id = App.StateProvinces.GetCountryStateIdByNameOrAbbreviation(country_id, state);
10618        _logger.Log('state_id: ' + state_id);
10619
10620        _logger.Log('zip: ' + zip);
10621
10622        if (thisApi.ContactAddressStreet1Field != null && thisApi.ContactAddressStreet1Field.Value != street1Formatted)
10623            _trySetFieldValue(thisApi.ContactAddressStreet1Field, street1Formatted);
10624
10625        if (thisApi.ContactAddressCityField != null && thisApi.ContactAddressCityField.Value != city)
10626            _trySetFieldValue(thisApi.ContactAddressCityField, city);
10627
10628        //need to set country and update states/provinces before setting value
10629        if (thisApi.ContactAddressCountryField != null && thisApi.ContactAddressCountryField.Value != country_id) {
10630            _trySetFieldValue(thisApi.ContactAddressCountryField, country_id);
10631            App.StateProvinces.RepopulateStatesDropDown(thisApi.ContactAddressStateProvinceField.DomElement, country_id);
10632        }
10633
10634        if (thisApi.ContactAddressStateProvinceField != null && thisApi.ContactAddressStateProvinceField.Value != state_id)
10635            _trySetFieldValue(thisApi.ContactAddressStateProvinceField, state_id);
10636
10637        if (thisApi.ContactAddressPostalCodeField != null && thisApi.ContactAddressPostalCodeField.Value != zip)
10638            _trySetFieldValue(thisApi.ContactAddressPostalCodeField, zip);
10639
10640        //todo: this should be triggered if there's error conditions
10641        let badAddress = doesGoogleAddressRequireUserInteraction(selectedPlace);
10642        if (badAddress)
10643            _showAllContactAddressFields();
10644
10645    };
10646
10647    const _hideAllTributeAddressFields = function () {
10648
10649        for (let x = 0; x < _allFieldApis.length; x++)
10650            if (_allFieldApis[x].IsTributeRecipientAddressInformation)
10651                _allFieldApis[x].Hide(false);
10652
10653        let showDetLink = document.getElementById('tributeAddressSingleLineEntryRowShowDetailsLink');
10654        if (showDetLink != null) { 
10655
10656            showDetLink.setAttribute('title', 'edit details');
10657
10658            let icon = showDetLink.querySelector('.ce-formFieldEditButtonIcons');
10659            if (icon != null) {
10660
10661                if (icon.hasClassName('fa')) {
10662                    icon.removeClassName('fa-close');
10663                    icon.addClassName('fa-edit');
10664                }
10665                else
10666                    icon.innerText = 'add unit #';
10667
10668            }
10669
10670            //showDetLink.innerText = 'edit';
10671
10672        }
10673
10674    };
10675
10676    const _showAllTributeAddressFields = function () {
10677
10678        for (let x = 0; x < _allFieldApis.length; x++)
10679            if (_allFieldApis[x].IsTributeRecipientAddressInformation)
10680                _allFieldApis[x].Show();
10681
10682        let showDetLink = document.getElementById('tributeAddressSingleLineEntryRowShowDetailsLink');
10683        if (showDetLink != null) { 
10684
10685            showDetLink.setAttribute('title', 'done editing');            
10686
10687            let icon = showDetLink.querySelector('.ce-formFieldEditButtonIcons');
10688            if (icon != null) {
10689
10690                if (icon.hasClassName('fa')) {
10691                    icon.removeClassName('fa-edit');
10692                    icon.addClassName('fa-close');
10693                }
10694                else
10695                    icon.innerText = 'done';
10696                
10697            }
10698
10699            //showDetLink.innerText = 'done'; //todo: future config to allow text versus icon - TBD
10700
10701        }
10702
10703    };
10704
10705    const _handleTributeSingleLineAddressShowDetailsClick = function () {
10706
10707        let showDetLink = document.getElementById('tributeAddressSingleLineEntryRowShowDetailsLink');
10708        if (showDetLink != null) {
10709
10710            let icon = showDetLink.querySelector('.ce-formFieldEditButtonIcons');
10711            if (icon != null) {
10712                 
10713                if (icon.innerText.containsValue('done') || icon.hasClassName('fa-close'))
10714                    _hideAllTributeAddressFields();
10715                else
10716                    _showAllTributeAddressFields();
10717
10718            }
10719
10720            //todo: future config to allow text versus icon - TBD            
10721            //if (showDetLink.innerText == 'edit')
10722            //    _showAllTributeAddressFields();
10723            //else
10724            //    _hideAllTributeAddressFields();
10725
10726        }
10727
10728    };
10729
10730    const _handleTributeSingleLineAddressIntegrationFailure = function () {
10731
10732        let showDetLink = document.getElementById('tributeAddressSingleLineEntryRow');
10733        if (showDetLink != null) {
10734
10735            showDetLink.vanish();
10736
10737            _showAllContactAddressFields();
10738
10739        }
10740
10741    };
10742
10743    const _handleTributeSingleLineAddressSelected = function (result) {
10744
10745        _logger.Log('tribute single line address selected');
10746
10747        let selectedPlace = result.Result;
10748
10749        let street1Formatted = parseGoogleAddressLine1Short(selectedPlace);
10750        let city = parseGoogleCityShortName(selectedPlace);
10751        let state = parseGoogleStateShortName(selectedPlace);
10752        let zip = parseGooglePostalCodeLongName(selectedPlace);
10753        let country = parseGoogleCountryShortName(selectedPlace);         
10754        let country_id = App.Countries.GetCountryIdByCodeOrAliasOrUsa(country);         
10755        let state_id = App.StateProvinces.GetCountryStateIdByNameOrAbbreviation(country_id, state);
10756         
10757        if (thisApi.TributeRecipientAddressStreet1Field != null && thisApi.TributeRecipientAddressStreet1Field.Value != street1Formatted)
10758            _trySetFieldValue(thisApi.TributeRecipientAddressStreet1Field, street1Formatted);
10759
10760        if (thisApi.TributeRecipientAddressCityField != null && thisApi.TributeRecipientAddressCityField.Value != city)
10761            _trySetFieldValue(thisApi.TributeRecipientAddressCityField, city);
10762
10763        //need to set country and update states/provinces before setting value
10764        if (thisApi.TributeRecipientAddressCountryField != null && thisApi.TributeRecipientAddressCountryField.Value != country_id) {
10765            _trySetFieldValue(thisApi.TributeRecipientAddressCountryField, country_id);
10766            App.StateProvinces.RepopulateStatesDropDown(thisApi.TributeRecipientAddressStateField.DomElement, country_id);
10767        }
10768
10769        if (thisApi.TributeRecipientAddressStateField != null && thisApi.TributeRecipientAddressStateField.Value != state_id)
10770            _trySetFieldValue(thisApi.TributeRecipientAddressStateField, state_id);
10771
10772        if (thisApi.TributeRecipientAddressPostalCodeField != null && thisApi.TributeRecipientAddressPostalCodeField.Value != zip)
10773            _trySetFieldValue(thisApi.TributeRecipientAddressPostalCodeField, zip);
10774
10775        //todo: this should be triggered if there's error conditions
10776        let badAddress = doesGoogleAddressRequireUserInteraction(selectedPlace);
10777        if (badAddress)
10778            _showAllTributeAddressFields();
10779
10780    };
10781
10782    let _googleMapsFeaturesInitialized = false;
10783
10784    const _setFormFieldButtonText = function (btnElem, text) { btnElem.firstElementChild.innerText = text; }
10785    const _addFormFieldButtonIconCss = function (btnElem, iconCss) { btnElem.firstElementChild.addClassNames(iconCss); }    
10786    const _setFormFieldButtonToolTip = function (btnElem, toolTip) { btnElem.setAttribute('title', toolTip); }   
10787
10788    const _generateFormFieldButton = function (domId, clickCallbackFunction, toolTip) {
10789
10790        let retVal = document.createElement('button');
10791        retVal.id = domId;
10792        retVal.setAttribute('type', 'button');        
10793        
10794        if (!isBlank(toolTip))
10795            _setFormFieldButtonToolTip(retVal, toolTip);            
10796
10797        retVal.addClassName('ce-formFieldButtons');
10798
10799        if (typeof clickCallbackFunction == 'function')
10800            retVal.addEventListener('click', function () { clickCallbackFunction(this); });
10801
10802        let cntElem = document.createElement('span');
10803
10804        cntElem.addClassName('ce-formFieldButtonContentContainers');
10805         
10806        retVal.appendChild(cntElem);
10807
10808        return retVal;
10809
10810    };
10811
10812    const _generateFieldToggleEditButton = function (domId, renderAsIcon, clickCallbackFunction) {
10813
10814        let retVal = document.createElement('button');
10815        retVal.id = domId;
10816        retVal.setAttribute('type', 'button');        
10817        retVal.setAttribute('title', 'edit details');
10818
10819        retVal.addClassName('ce-formFieldEditButtons');
10820
10821        if (typeof clickCallbackFunction == 'function')
10822            retVal.addEventListener('click', function () { clickCallbackFunction(); });
10823
10824        let icon = document.createElement('span');
10825
10826        icon.addClassName('ce-formFieldEditButtonIcons');
10827
10828        if (renderAsIcon)
10829            icon.addClassNames('fa fa-edit');
10830        else
10831            icon.innerText = 'edit';        
10832
10833        retVal.appendChild(icon);
10834
10835        return retVal;
10836
10837    };
10838   
10839    const _initializeGoogleMapsDependentFeatures = function () {
10840
10841        if (!_googleMapsFeaturesInitialized) {
10842
10843            _googleMapsFeaturesInitialized = true;
10844
10845            let firstContactInfoField = _getFirstEnabledContactAddressField();
10846            if (firstContactInfoField != null) {
10847
10848                let row = _createFormRow('contactAddressSingleLineEntryRow', 'ce-addressSingleLineEntryRows');
10849
10850                let fld = _createFormFieldContainer('contactAddressSingleLineEntryField');
10851                 
10852                let fldWrapper = document.createElement('div');
10853                fldWrapper.id = 'contactSingleLineAddressFieldWrapper';
10854                fldWrapper.addClassNames('ce-formFieldWrappers ce-singleLineAddressFieldWrappers');
10855
10856                /*
10857                New Google Maps API
10858                let contactSingleLineInput = new google.maps.places.PlaceAutocompleteElement();
10859                //only available in BETA release of maps for now
10860                contactSingleLineInput.noClearButton = true;
10861                contactSingleLineInput.setAttribute('class', 'ce-googleMapsAutocomplete');
10862                */
10863
10864               //LEGACY GOOGLE MAPS API
10865                let contactSingleLineInput = document.createElement('input');
10866                contactSingleLineInput.setAttribute('type', 'text');
10867                //LEGACY GOOGLE MAPS API
10868
10869                contactSingleLineInput.id = 'contactAddressSingleLineInput';
10870                contactSingleLineInput.setAttribute('placeholder', 'Start typing address...');
10871
10872                let contactSingleLineInputLbl = document.createElement('label');
10873                contactSingleLineInputLbl.setAttribute('for', contactSingleLineInput.id);
10874                contactSingleLineInputLbl.innerHTML = 'Address';
10875                contactSingleLineInputLbl.addClassName('ce-formFieldLabels');
10876
10877                fld.appendChild(contactSingleLineInputLbl);
10878
10879                fldWrapper.appendChild(contactSingleLineInput);
10880
10881                let editToggleBtn = _generateFieldToggleEditButton('contactAddressSingleLineEntryRowShowDetailsLink', false, function () { _handleContactSingleLineAddressShowDetailsClick(); });
10882
10883                fldWrapper.appendChild(editToggleBtn); //fldWrapper.addHtmlClickLink('hide details', function () { _handleContactSingleLineAddressShowDetail
10883sClick(); }, 'contactAddressSingleLineEntryRowShowDetailsLink'); //todo: future config to allow text versus icon - TBD
10884
10885                fld.appendChild(fldWrapper);
10886
10887                row.appendChild(fld);
10888
10889                firstContactInfoField.RowDomElement.insertBeforeElement(row);
10890
10891                _hideAllContactAddressFields();
10892
10893                thisApi.ContactSingleLineAddressApi = getSingleLineAddressFormFieldApi({ "AutoInitialize": false, "SingleLineAddressTextBoxDomId": contactSingleLineInput.id }); //New Google Maps API, "PlaceAutocompleteElement": contactSingleLineInput });
10894                thisApi.ContactSingleLineAddressApi.RegisterAddressSelectedCallBack(_handleConactSingleLineAddressSelected);
10895                thisApi.ContactSingleLineAddressApi.RegisterGoogleMapsIntegrationFailureCallBack(_handleContactSingleLineAddressIntegrationFailure);
10896                thisApi.ContactSingleLineAddressApi.Initialize();
10897
10898                _initializeDynamicLabelField(_tryGetContactAddressSingleLineEntryDomElement(),contactSingleLineInputLbl);
10899
10900            }
10901            else
10902                _logger.Log('skip contact single line address setup (not contact address fields)');
10903
10904            let firstTributeContactInfField = _getFirstTributeRecipientContactInfoField();
10905            if (firstTributeContactInfField != null) {
10906
10907                let row = _createFormRow('tributeAddressSingleLineEntryRow', 'ce-addressSingleLineEntryRows');
10908
10909                let fld = _createFormFieldContainer('tributeAddressSingleLineEntryField');
10910                 
10911                let fldWrapper = document.createElement('div');
10912                fldWrapper.id = 'tributeSingleLineAddressFieldWrapper';
10913                fldWrapper.addClassNames('ce-formFieldWrappers ce-singleLineAddressFieldWrappers');
10914
10915                let tributeSingleLineInput = document.createElement('input');
10916                tributeSingleLineInput.setAttribute('type', 'text');
10917                tributeSingleLineInput.id = 'tributeAddressSingleLineInput';
10918                tributeSingleLineInput.setAttribute('placeholder', 'Start typing address...');
10919
10920                let tributeSingleLineInputLbl = document.createElement('label');
10921                tributeSingleLineInputLbl.setAttribute('for', tributeSingleLineInput.id);
10922                tributeSingleLineInputLbl.innerHTML = 'Address';
10923                tributeSingleLineInputLbl.addClassName('ce-formFieldLabels');
10924
10925                fld.appendChild(tributeSingleLineInputLbl);
10926
10927                fldWrapper.appendChild(tributeSingleLineInput);
10928
10929                let editToggleBtn = _generateFieldToggleEditButton('tributeAddressSingleLineEntryRowShowDetailsLink', false, function () { _handleTributeSingleLineAddressShowDetailsClick(); });
10930
10931                fldWrapper.appendChild(editToggleBtn); //fldWrapper.addHtmlClickLink('hide details', function () { _handleTributeSingleLineAddressShowDetailsClick(); }, 'tributeAddressSingleLineEntryRowShowDetailsLink');
10932
10933                fld.appendChild(fldWrapper);
10934
10935                row.appendChild(fld);
10936
10937                firstTributeContactInfField.RowDomElement.insertBeforeElement(row);
10938
10939                _hideAllTributeAddressFields();
10940
10941                thisApi.TributeSingleLineAddressApi = getSingleLineAddressFormFieldApi({ "AutoInitialize": false, "SingleLineAddressTextBoxDomId": tributeSingleLineInput.id });
10942                thisApi.TributeSingleLineAddressApi.RegisterAddressSelectedCallBack(_handleTributeSingleLineAddressSelected);
10943                thisApi.TributeSingleLineAddressApi.RegisterGoogleMapsIntegrationFailureCallBack(_handleTributeSingleLineAddressIntegrationFailure);
10944                thisApi.TributeSingleLineAddressApi.Initialize();
10945                
10946                _initializeDynamicLabelField(_tryGetTributeRecipientAddressSingleLineEntryDomElement(),tributeSingleLineInputLbl);
10947
10948            }
10949            else
10950                _logger.Log('skip tribute single line address setup (not tribute address fields)');
10951
10952        }
10953
10954    };
10955
10956    const _googleMapsScriptLoadedHandler = function () {
10957
10958        _logger.Log('google maps location script loaded (handler started)');
10959
10960        if (_initializeCompletedPreCallBacks) {
10961
10962            _logger.Log('fire google maps dependent feature setup rigth away (initialization completed)');
10963
10964            _initializeGoogleMapsDependentFeatures();
10965
10966        }
10967        else
10968        {
10969
10970            _logger.Log('register google maps dependent feature form ready call back (controlled timing & predictable results)');
10971
10972            thisApi.RegisterFormReadyCallBack(function () { _initializeGoogleMapsDependentFeatures() });
10973
10974        }
10975
10976    };
10977
10978    thisApi.HandleGoogleMapsScriptLoaded = function () { _googleMapsScriptLoadedHandler(); };
10979
10980    //CORE Processing APIs (process transactions)
10981    //*******************************************
10982
10983    thisApi.ProcessTransaction = function () { _processTransaction(); };
10984
10985    const _resetExpiredRequestTokenAutoRetryCounter = function () {
10986        App.LocalStorage.Save('webFormExpiredTokenResubmitCount',0); //reset
10987    };
10988
10989    const _processTransaction = function (userConfirmedNotDuplicate) {        
10990
10991        let validationSucceeded = thisApi.Validate();
10992
10993        _logger.Log('process transaction clicked (validation: ' + validationSucceeded + ')');
10994
10995        _clearHeaderMessageContent();
10996
10997        if (!validationSucceeded) 
10998            _setHeaderErrorContent('Error', thisApi.FormValidationMessage);                    
10999            
11000        if (validationSucceeded) {
11001
11002            //check for confirmation message needed
11003            let confirmProcess = true;
11004            if (!isBlank(WebFormContext.Form.NextButtonConfirmationMessage))
11005                confirmProcess = confirm(WebFormContext.Form.NextButtonConfirmationMessage);
11006
11007            if (confirmProcess) {
11008                 
11009                let initiative_id = _tryGetFormContextIdOrZero(WebFormContext.Form.DefaultInitiative_Id);
11010
11011                let contextInitiative_id = _tryGetFormContextIdOrZero(WebFormContext.Form.ContextInitiative_Id);  
11012                if (contextInitiative_id > 0)
11013                    initiative_id = contextInitiative_id;
11014
11015                let audienceSegment_id = _tryGetFormContextIdOrZero(WebFormContext.Form.DefaultAudienceSegment_Id);  
11016            
11017                let contextAudienceSegment_id = _tryGetFormContextIdOrZero(WebFormContext.Form.ContextAudienceSegment_Id);  
11018                if (contextAudienceSegment_id > 0)
11019                    audienceSegment_id = contextAudienceSegment_id;
11020                
11021                let glCode_id = _tryGetFormContextIdOrZero(WebFormContext.Form.GeneralLedgerCode_Id);
11022
11023                let defFund_id = _tryGetFormContextIdOrZero(WebFormContext.Form.DefaultFund_Id);
11024                let defRegFund_id = _tryGetFormContextIdOrZero(WebFormContext.Form.DefaultEventRegistrationFund_Id);
11025
11026                let donationFundToUse = defFund_id;
11027
11028                if (thisApi.FundId > 0)
11029                    donationFundToUse = thisApi.FundId;
11030
11031                let transAmt = thisApi.GetTransactionAmount(); 
11032                let donationAmt = thisApi.GenericAmountInputsAmount;
11033
11034                let coverFeesAmt = _calculateFees();
11035
11036                let taxDeductibleAmt = thisApi.GetTransactionTaxDeductibleAmount();
11037
11038                //TODO: Make fees being tax deductible configurable at the form or client level
11039                //No fees for DAFPay
11040                if (thisApi.PaymentMethodId != PAYMENT_METHODS.SOFT_CREDIT)
11041                    taxDeductibleAmt += coverFeesAmt;
11042
11043                let fundsArray = [];  
11044
11045                let tixCnt = thisApi.GetTicketCount();
11046                if (tixCnt > 0 && defRegFund_id > 0 && donationFundToUse != defRegFund_id && transAmt > donationAmt) {
11047
11048                    fundsArray.push({ "Id": defRegFund_id, "Amount": (transAmt - donationAmt) });
11049
11050                    if(donationFundToUse > 0)
11051                        fundsArray.push({ "Id": donationFundToUse, "Amount": donationAmt });
11052
11053                }
11054                else if (donationFundToUse > 0)
11055                    fundsArray.push({ "Id": donationFundToUse, "Amount": transAmt });     
11056
11057                let glCodesArray = thisApi.GetTicketGLCodeMapping();
11058
11059                if (glCode_id > 0 && donationAmt > 0) {
11060
11061                    let glCodeIndex = glCodesArray.findIndex(e => e.Id === glCode_id);
11062
11063                    if (glCodeIndex > -1)
11064                        glCodesArray[glCodeIndex].Amount += donationAmt;
11065                    else
11066                        glCodesArray.push({ "Id": glCode_id, "Amount": donationAmt });
11067                }
11068                 
11069                let solicitorContact_id = (thisApi.SolicitorId > 0) ? thisApi.SolicitorId : WebFormContext.Form.DefaultSolicitor_Id;
11070
11071                let transactionPayload = {
11072                    "Form_Id": thisApi.Form_Id,
11073                    "UserConfirmedNotDuplicate": userConfirmedNotDuplicate,                    
11074                    "PaymentAmount": transAmt,
11075                    "TaxDeductibleAmount": taxDeductibleAmt,
11076                    "Currency": thisApi.PaymentCurrencyId,               
11077                    "DonationAmount": thisApi.GenericAmountInputsAmount,  
11078                    "CoverFeesAmount": coverFeesAmt,          
11079                    "Category_Id": _tryGetFormContextIdOrZero(WebFormContext.Form.Category_Id),
11080                    "Contact": {
11081                        "ContactType_Id": thisApi.ContactTypeId,
11082                        "PersonInfo": {
11083                            "FirstName": thisApi.ContactFirstName,
11084                            "MiddleName": thisApi.ContactMiddleName,
11085                            "LastName": thisApi.ContactLastName,
11086                            "BirthDate": null,
11087                            "Gender": null
11088                        },
11089                        "OrganizationInfo": {
11090                            "Name": thisApi.OrganizationName,
11091                            "Type": null
11092                        },
11093                        //"HouseholdInfo": { //maybe in future
11094                        //    "Name": null
11095                        //},
11096                        "AddressStreet1": thisApi.ContactAddressStreet1,
11097                        "AddressStreet2": thisApi.ContactAddressStreet2,
11098                        "AddressCity": thisApi.ContactAddressCity,
11099                        "AddressState": thisApi.ContactAddressStateProvinceCode,
11100                        "AddressPostalCode": thisApi.ContactAddressPostalCode,
11101                        "AddressCountry": thisApi.ContactAddressCountryCode,
11102                        "EmailAddress": thisApi.ContactEmailAddress,
11103                        "PhoneNumber": thisApi.ContactPhoneNumber,
11104                        "FaxNumber": null,
11105                        "SmsNumber": null,
11106                        "MailOptIn": thisApi.MailOptIn,
11107                        "EmailOptIn": thisApi.EmailOptIn,
11108                        "PhoneOptIn": thisApi.PhoneOptIn,
11109                        "FaxOptIn": null,
11110                        "SmsOptIn": thisApi.SmsOptIn,
11111                        "OptInListIds": thisApi.SelectedEmailOptInListIds,
11112                        "GroupIds": thisApi.SelectedCrmGroupIds                    
11113                    },
11114                    "MakeAnonymous": thisApi.Anonymous,
11115                    "DisplayNameOnlineAs": null,
11116                    "HideAmountOnline": false,
11117                    "Comment": thisApi.Comment,
11118                    "PaymentBillingInfo": {
11119                        "AddressStreet1": thisApi.BillingAddressStreet1,
11120                        "AddressStreet2": thisApi.BillingAddressStreet2,
11121                        "AddressCity": thisApi.BillingAddressCity,
11122                        "AddressStateProvince": thisApi.BillingAddressStateProvinceCode,
11123                        "AddressPostalCode": thisApi.BillingAddressPostalCode,
11124                        "AddressCountry": thisApi.BillingAddressCountryCode
11125                    },
11126                    "Attribution": {
11127                        "AudienceSegment_Id": audienceSegment_id,
11128                        "Initiative_Id": initiative_id,
11129                        "ResponseChannel_Id": _tryGetFormContextIdOrZero(WebFormContext.Form.ResponseChannel_Id),
11130                        "Opportunity_Id": 0,
11131                        "Event_Id": _tryGetFormContextIdOrZero(WebFormContext.Form.Event_Id),
11132                        "GrassrootEvent_Id": thisApi.GrassrootsEventId,
11133                        "PeerToPeerMicrosite_Id": 0,
11134                        "PeerToPeerTeam_Id": 0,
11135                        "PeerToPeerPage_Id": 0,
11136                        "Tribute_Id": thisApi.TributeId, 
11137                        "Tribute": null, //populated below
11138                        "Funds": fundsArray,
11139                        "GeneralLedgerCodes": glCodesArray,
11140                        "Solicitor": {
11141                            "Contact_Id": solicitorContact_id
11142                        },
11143                        "CustomCodes": {
11144                            "Code1": thisApi.CustomCode1,
11145                            "Code2": thisApi.CustomCode2,
11146                            "Code3": thisApi.CustomCode3,
11147                            "Code4": thisApi.CustomCode4,
11148                            "Code5": thisApi.CustomCode5,
11149                            "Code6": thisApi.CustomCode6,
11150                            "Code7": thisApi.CustomCode7,
11151                            "Code8": thisApi.CustomCode8
11152                        }
11153                    }
11154                };
11155
11156                if (thisApi.FreeGiftSelectorProductId > 0) {
11157                    transactionPayload.FreeGift = {};
11158                    transactionPayload.FreeGift.Product_Id = thisApi.FreeGiftSelectorProductId;  
11159                    transactionPayload.FreeGift.FulfillmentCenter_Id = thisApi.FreeGiftSelectorfulfillmentCenterId;
11160                }
11161                
11162                //check for billing info same as contact info                
11163                if (thisApi.PaymentMethodId == PAYMENT_METHODS.CREDIT_CARD) {                
11164
11165                    let isBillingInfoBlank = (isBlank(transactionPayload.PaymentBillingInfo.AddressStreet1) && isBlank(transactionPayload.PaymentBillingInfo.AddressCity) && isBlank(transactionPayload.PaymentBillingInfo.AddressPostalCode));
11166
11167                    if ((document.getElementById('togglePaymentMethodBillingInfoCheckBox') == null && isBillingInfoBlank) || document.getElementById('togglePaymentMethodBillingInfoCheckBox').getValue()) { 
11168
11169                        transactionPayload.PaymentBillingInfo.AddressStreet1 = transactionPayload.Contact.AddressStreet1;
11170                        transactionPayload.PaymentBillingInfo.AddressStreet2 = transactionPayload.Contact.AddressStreet2;
11171                        transactionPayload.PaymentBillingInfo.AddressCity = transactionPayload.Contact.AddressCity;
11172                        transactionPayload.PaymentBillingInfo.AddressStateProvince = transactionPayload.Contact.AddressStateProvince;
11173                        transactionPayload.PaymentBillingInfo.AddressPostalCode = transactionPayload.Contact.AddressPostalCode;
11174                        transactionPayload.PaymentBillingInfo.AddressCountry = transactionPayload.Contact.AddressCountry;
11175
11176                    }
11177
11178                }
11179
11180                let trbRecipRbl = document.getElementById('tributeRecipientToggle');
11181                if (trbRecipRbl != null) {
11182
11183                    let tribTpParam = TRIBUTE_TYPES.HONOR;
11184
11185                    if (thisApi.TributeTypeId > 0)
11186                        tribTpParam = thisApi.TributeTypeId
11187
11188                    transactionPayload.Attribution.Tribute = {};
11189                    transactionPayload.Attribution.Tribute.Tribute_Id = 0;
11190                    transactionPayload.Attribution.Tribute.Type_Id = tribTpParam;
11191                    transactionPayload.Attribution.Tribute.TributeName = thisApi.TributeName;
11192
11193                    //thisApi.TributeDescription
11194
11195                    transactionPayload.Attribution.Tribute.Acknowedgees = [];
11196
11197                    let ack = {};                
11198                    ack.Message = thisApi.TributePersonalMessage;                
11199
11200                    ack.FirstName = thisApi.TributeRecipientFirstName;
11201                    ack.LastName = thisApi.TributeRecipientLastName;            
11202
11203                    ack.ECard_Id = 0;
11204                    ack.PrintCard_Id = 0;
11205                    ack.PrintCardCount = 0;
11206
11207                    let selVal = trbRecipRbl.getValue();
11208                    switch (selVal) {
11209                        case 'mail':
11210
11211                            ack.AddressStreet1 = thisApi.TributeRecipientAddressStreet1;  
11212                            ack.AddressStreet2 = thisApi.TributeRecipientAddressStreet2;
11213                            ack.AddressCity = thisApi.TributeRecipientAddressCity;
11214                            ack.AddressState = thisApi.TributeRecipientAddressStateCode;     
11215                            ack.AddressPostalCode = thisApi.TributeRecipientAddressPostalCode;
11216                            ack.AddressCountry = thisApi.TributeRecipientAddressCountryCode;
11217
11218                            ack.PrintCard_Id = thisApi.PrintCardId;
11219                            ack.PrintCardCount = thisApi.PrintCardCount;
11220
11221                            break;
11222                        case 'email':
11223
11224                            ack.EmailAddress = thisApi.TributeRecipientEmailAddress;  
11225
11226                            if(_isTributeEcardOptionSelected())
11227                                ack.ECard_Id = thisApi.ECardId;
11228
11229                            break;
11230                        case 'phone':
11231                            //thisApi.TributeRecipientPhoneNumber
11232                            break;
11233                    }
11234                 
11235                    transactionPayload.Attribution.Tribute.Acknowedgees.push(ack);
11236
11237                }
11238 
11239                if (thisApi.PromoCodeFieldApi != null && thisApi.PromoCodeFieldApi.ValidateResponse != null && thisApi.PromoCodeFieldApi.ValidateResponse.Success) {
11240
11241                    transactionPayload.PromoCode = {};
11242                    transactionPayload.PromoCode.Code = thisApi.PromoCodeFieldApi.ValidateResponse.PromoCode;
11243                    transactionPayload.PromoCode.Promo_Id = thisApi.PromoCodeFieldApi.ValidateResponse.Promo_Id;
11244                    transactionPayload.PromoCode.PromoCode_Id = thisApi.PromoCodeFieldApi.ValidateResponse.PromoCode_Id;
11245                    transactionPayload.PromoCode.Discount = thisApi.GetDiscountAmount();
11246                    transactionPayload.PromoCode.RegistrationDiscount = thisApi.PromoCodeFieldApi.ValidateResponse.TicketsAppliedDiscount;
11247                    transactionPayload.PromoCode.NonRegistrationDiscount = thisApi.PromoCodeFieldApi.ValidateResponse.NonTicketAppliedDiscount;
11248
11249                }
11250
11251                let relatedAccountMode = '';
11252
11253                if (thisApi.MembershipTypeId > 0)
11254                    relatedAccountMode = 'membership';
11255                else if (WebFormContext.Form.Type.Id == FORM_TYPES.Pledge && thisApi.RecurringPaymentStartDateField != null)
11256                    relatedAccountMode = 'pledge';
11257                else if (thisApi.RecurringPaymentFrequencyId > 0)
11258                    relatedAccountMode = 'sustainer';
11259
11260                switch (relatedAccountMode) {
11261                    case 'membership':
11262
11263                        let membRate = _getSelectedMembershipRate();
11264                        let memTaxDeductibleAmt = _getSelectedMembershipTaxDeductibleAmount();
11265                 
11266                        transactionPayload.Membership = {};
11267                        transactionPayload.Membership.Type_Id = thisApi.MembershipTypeId; 
11268                        transactionPayload.Membership.Rate = membRate;
11269                        transactionPayload.Membership.TaxDeductibleAmount = memTaxDeductibleAmt;
11270
11271                        transactionPayload.Membership.PaymentPlanFrequency_Id = _getMembershipPaymentPlanFrequencyId(); 
11272
11273                        //need to consider implications of cover fees split with additional donation (handle server side for now)
11274                        //transactionPayload.Membership.PaymentPlanAmount = membRate; 
11275                        //transactionPayload.Membership.PaymentPlanTaxDeductibleAmount = memTaxDeductibleAmt; 
11276                     
11277                        let memStartDate = thisApi.RecurringPaymentStartDate;
11278                        if (memStartDate == null)
11279                            memStartDate = new Date();
11280
11281                        transactionPayload.Membership.StartDate = formatDotNetJsonDate(memStartDate);
11282             
11283                        //annual memberships for now
11284                        let durationDays = 365;
11285
11286                        let dd = _getSelectedMembershipDurationDays();
11287                        if (dd > 0)
11288                            durationDays = dd;
11289
11290                        let memEndDate = null;
11291
11292                        //_logger.Log('thisApi.AutoRenewMembership: ' + thisApi.AutoRenewMembership);
11293                        if (!thisApi.AutoRenewMembership) { 
11294                            memEndDate = new Date();
11295                            memEndDate = memEndDate.setDate(memStartDate.getDate() + durationDays);
11296                            transactionPayload.Membership.EndDate = formatDotNetJsonDate(memEndDate);
11297                        }
11298                     
11299                        //add sub memberships
11300                        let allowSubMembers = getMultiSelectInputSelectedOptionBoolAttributeValue(thisApi.MembershipTypeField.DomElement, 'data-membership-allow-sub-members');
11301                        if (allowSubMembers) 
11302                            transactionPayload.Membership.SubMembers = _membershipAdditionalContactApi.GetContactDetails();                
11303                 
11304                        //TODO: add custom installment if needed in future - e.g. Membership.Installment.push();
11305
11306                        break;
11307                    case 'pledge':
11308                     
11309                        transactionPayload.Pledge = {};
11310                        transactionPayload.Pledge.Type_Id =  thisApi.PledgeTypeId;
11311                        transactionPayload.Pledge.Amount = thisApi.FirstGenericAmountInputAmount + coverFeesAmt;
11312                        transactionPayload.Pledge.TaxDeductibleAmount = thisApi.FirstGenericTaxDeductibleAmountInputAmount + coverFeesAmt;
11313
11314                        transactionPayload.Pledge.Invoices = [];
11315
11316                        let frstInvoice = {};
11317                        frstInvoice.DueDate = thisApi.RecurringPaymentStartDateAjaxFormat;
11318                        frstInvoice.Amount = transactionPayload.Pledge.Amount;
11319                        frstInvoice.TaxDeductibleAmount = transactionPayload.Pledge.TaxDeductibleAmount;
11320
11321                        transactionPayload.Pledge.Invoices.push(frstInvoice);
11322
11323                        //update payment amount based on installements or future date
11324                        if (thisApi.RecurringPaymentStartDate != null && thisApi.RecurringPaymentStartDate > new Date()) {
11325                            transactionPayload.PaymentAmount = 0.00;
11326                            transactionPayload.TaxDeductibleAmount = 0.00;
11327                        }
11328
11329                        break;
11330                    case 'sustainer':
11331
11332                        let subsType_id = 0; //@webFormSettings.def_recurringSubscriptionType_id
11333
11334                        transactionPayload.Sustainer = {};
11335                        transactionPayload.Sustainer.Type_Id = subsType_id;
11336                        transactionPayload.Sustainer.Amount = thisApi.RecurringPaymentAmount + coverFeesAmt;
11337                        transactionPayload.Sustainer.TaxDeductibleAmount = thisApi.RecurringPaymentTaxDeductibleAmount + coverFeesAmt;
11338                        transactionPayload.Sustainer.Frequency_Id = thisApi.RecurringPaymentFrequencyId;
11339                        transactionPayload.Sustainer.StartDate = thisApi.RecurringPaymentStartDateAjaxFormat;
11340                        transactionPayload.Sustainer.EndDate = thisApi.RecurringPaymentEndDateAjaxFormat;
11341                        transactionPayload.Sustainer.TotalAmountLimit = thisApi.RecurringMaxAmount;
11342
11343                        //update payment amount based on installements or future date
11344                        if (thisApi.RecurringPaymentStartDate != null && thisApi.RecurringPaymentStartDate > new Date()) {
11345                            transactionPayload.PaymentAmount = 0.00;
11346                            transactionPayload.TaxDeductibleAmount = 0.00;
11347                        }
11348
11349                        break;
11350                }
11351
11352                //add tickets
11353                //duplicated (used above)
11354                //let tixCnt = thisApi.GetTicketCount();
11355                if (tixCnt > 0) {
11356
11357                    transactionPayload.Registration = {};
11358                    transactionPayload.Registration.DietaryRestrictions = thisApi.DietaryRestriction;
11359                    transactionPayload.Registration.EmergencyContactName = thisApi.EmergencyContactName;
11360                    transactionPayload.Registration.EmergencyContactPhoneNumber = thisApi.EmergencyContactPhoneNumber;
11361                    transactionPayload.Registration.SelectedTickets = [];
11362                 
11363                    for (let x = 0; x < thisApi.TicketQuantityFields.length; x++)
11364                        if (thisApi.TicketQuantityFields[x].DomElement != null && thisApi.TicketQuantityFields[x].Value > 0) {
11365
11366                            let tick_id = thisApi.TicketQuantityFields[x].DomElement.tryGetIntegerAttribute('data-ticket-id');
11367
11368                            let newTick = {};
11369
11370                            newTick.Ticket_Id = tick_id;
11371                            newTick.Quantity = thisApi.TicketQuantityFields[x].Value;
11372                            newTick.Price = thisApi.TicketQuantityFields[x].DomElement.tryGetDecimalAttribute('data-ticket-price');
11373                            newTick.TaxDeductible = thisApi.TicketQuantityFields[x].DomElement.tryGetDecimalAttribute('data-ticket-tax-deductible-amount');
11374                        
11375                            newTick.RegistrationStatus_Id = 0;
11376                            newTick.RegistrantCheckedIn = false;
11377
11378                            newTick.Session_Id = 0;
11379
11380                            if (thisApi.TicketSessionFields != null && thisApi.TicketSessionFields.length > 0)
11381                                for (let y = 0; y < thisApi.TicketSessionFields.length; y++)
11382                                    if (thisApi.TicketSessionFields[y].DomElement.tryGetIntegerAttribute('data-ticket-id') == tick_id) {
11383                                        newTick.Session_Id = thisApi.TicketSessionFields[y].Value.toIntZeroIfInvalid();
11384                                        break;
11385                                    }
11386
11387                            newTick.DifferentRegistrantInfo = _getTicketMeOrSomeoneElseApi(tick_id)?.GetContactDetails();
11388
11389                            newTick.AdditionalRegistrants = _getMultipleTicketsOtherRegistrantsApi(tick_id)?.GetContactDetails();
11390
11391                            newTick.Guests = _getTicketAdditionalContactsApi(tick_id)?.GetContactDetails();
11392
11393                            transactionPayload.Registration.SelectedTickets.push(newTick);
11394
11395                        }
11396
11397                }
11398
11399                //volunteer posts            
11400                if (thisApi.SelectedVolunteerPostIds.length > 0) {
11401
11402                    transactionPayload.VolunteerPostSignUpInfo = {};
11403
11404                    transactionPayload.VolunteerPostSignUpInfo.SelectedPostIds = thisApi.SelectedVolunteerPostIds;
11405
11406                }
11407
11408                //volunteer post time entry
11409                let volunteerPostTimeEntryWrappers = document.querySelectorAll('.ce-volunteerPostTimeEntryFieldRows');
11410                if (volunteerPostTimeEntryWrappers.length > 0) { 
11411
11412                    transactionPayload.VolunteerTimeEntryInfo = [];
11413
11414                    for (let x = 0; x < volunteerPostTimeEntryWrappers.length; x++) {
11415
11416                        let rowElem = volunteerPostTimeEntryWrappers[x];
11417
11418                        let post_id = rowElem.tryGetIntegerAttribute('data-volunteer-post-id');
11419                        let startDate = rowElem.querySelector('.ce-volunteerPostTimeEntryStartDates').getValue().toDotNetAjaxDateTimeOrNull();
11420                        let endDate = rowElem.querySelector('.ce-volunteerPostTimeEntryEndDates').getValue().toDotNetAjaxDateTimeOrNull();
11421
11422                        if (post_id > 0 && startDate != null && endDate != null) { 
11423
11424                            let tei = {};
11425                            tei.VolunteerPost_Id = post_id;
11426                            tei.StartTime = startDate;
11427                            tei.EndTime = endDate;
11428
11429                            transactionPayload.VolunteerTimeEntryInfo.push(tei);
11430
11431                        }
11432
11433                    }
11434
11435                }
11436
11437                //cases
11438                //WebFormContext.Form.Type.Id == FORM_TYPES.Case_Intake && 
11439                if (thisApi.CaseTitleField != null || thisApi.CaseDescription != null) {
11440
11441                    transactionPayload.CaseInfo = {};
11442
11443                    if (thisApi.CaseTitleField != null)
11444                        transactionPayload.CaseInfo.Title = thisApi.CaseTitle;
11445                    else
11446                        transactionPayload.CaseInfo.Title = 'Online Case Submission';
11447
11448                    transactionPayload.CaseInfo.Status_Id = thisApi.CaseStatusId;
11449                    transactionPayload.CaseInfo.Priority_Id = thisApi.CasePriorityId;
11450                    transactionPayload.CaseInfo.Description = thisApi.CaseDescription;
11451
11452                }
11453
11454                //products
11455                if (typeof WebFormContext.Form.ProductWidgetApis != 'undefined' && WebFormContext.Form.ProductWidgetApis.length > 0) {
11456
11457                    transactionPayload.Products = [];
11458
11459                    for (let x = 0;x < WebFormContext.Form.ProductWidgetApis.length;x++)                         
11460                        if(WebFormContext.Form.ProductWidgetApis[x].SelectionDetail
11460s.Quantity > 0){
11461                            
11462                            //console.log('SelectionDetails: ' + JSON.stringify(WebFormContext.Form.ProductWidgetApis[x].SelectionDetails));
11463
11464                            transactionPayload.Products.push(WebFormContext.Form.ProductWidgetApis[x].SelectionDetails);
11465
11466                        }
11467
11468                }
11469
11470                //_logger.Log('build payload with _oneClickProfile_id: ' + thisApi.OneClickCheckOutApiPaymentProfile_Id);
11471
11472                if (thisApi.OneClickCheckOutApi != null && thisApi.OneClickCheckOutApi.EnabledByUser && thisApi.OneClickCheckOutApi.PaymentProfile_Id > 0) 
11473                    transactionPayload.OneClickProfile_Id = thisApi.OneClickCheckOutApi.PaymentProfile_Id;                             
11474                else { 
11475
11476                    _logger.Log('build payload with payment method: ' + thisApi.PaymentMethodId);
11477
11478                    switch (thisApi.PaymentMethodId) {
11479                        case PAYMENT_METHODS.CREDIT_CARD:
11480
11481                            let payWalletTypeId = _getPaymentWalletTypeId();
11482                            switch (payWalletTypeId) {
11483                                case WALLET_TYPES.NONE:
11484
11485                                    let expMonth = thisApi.CreditCardExpirationMonth;
11486                                    let expYear = thisApi.CreditCardExpirationYear;
11487                                 
11488                                    const parsedExpDates = parseExpirationDateParts(thisApi.CreditCardExpirationDate);
11489                                    if (!isBlank(parsedExpDates.month) && !isBlank(parsedExpDates.year)) {
11490                                        expMonth = parsedExpDates.month;
11491                                        expYear = parsedExpDates.year;
11492                                    }
11493
11494                                    transactionPayload.CreditCardInfo = {
11495                                        "CardNumber": thisApi.CreditCardNumber,
11496                                        "ExpirationMonth": expMonth,
11497                                        "ExpirationYear": expYear,
11498                                        "NameOnCard": thisApi.CreditCardHolderName,
11499                                        "CVV": thisApi.CreditCardSecurityCode
11500                                    };
11501
11502                                    break;
11503                                case WALLET_TYPES.GOOGLE_PAY:
11504
11505                                    transactionPayload.ThirdPartyPaymentInfo = {
11506                                        "PlatformId": THIRD_PARTY_PAYMENT_PLATFORMS.GooglePay,
11507                                        "PlatformToken": _walletToken,
11508                                        "PlatformTokenExpirationMonth": _tokenExpirationMonth,
11509                                        "PlatformTokenExpirationYear": _tokenExpirationYear,
11510                                        "PlatformWalletCreditCardTypeId": _walletCreditCardType_id, //is this applicable to GooglePay?
11511                                    };
11512
11513                                    break;
11514                                case WALLET_TYPES.APPLE_PAY:
11515
11516                                    transactionPayload.ThirdPartyPaymentInfo = {
11517                                        "PlatformId": THIRD_PARTY_PAYMENT_PLATFORMS.ApplePay,
11518                                        "PlatformToken": _walletToken,
11519                                        "PlatformTokenExpirationMonth": _tokenExpirationMonth,
11520                                        "PlatformTokenExpirationYear": _tokenExpirationYear,
11521                                        "PlatformWalletCreditCardTypeId": _walletCreditCardType_id,
11522                                        "PlatformWalletEciIndicatorId": _walletEciIndicator,
11523                                        "PlatformTokenCryptoGram": _tokenCryptogram
11524                                    };
11525
11526                                    break;
11527                            }
11528
11529                            break;
11530                        case PAYMENT_METHODS.ACH:
11531
11532                            transactionPayload.BankAccountInfo = {
11533                                "BankAccountType": thisApi.BankAccountTypeId,
11534                                "BankName": null,
11535                                "BankCity": null,
11536                                "BankStateProvince": null,
11537                                "RoutingNumber": thisApi.BankRoutingNumber,
11538                                "AccountNumber": thisApi.BankAccountNumber,
11539                                "NameOnAccount": thisApi.BankAccountNameOnAccount
11540                            };
11541
11542                            break;
11543                        case PAYMENT_METHODS.PAYPAL:
11544
11545                            transactionPayload.ThirdPartyPaymentInfo = {
11546                                "PlatformId": THIRD_PARTY_PAYMENT_PLATFORMS.PayPal,
11547                                "PlatformToken": _payPalTokenId,
11548                                "PayerAccountId": _payPalGatewayPayerId,
11549                                "ChargedAmount": _payPalAmountChargedResponse
11550                            };
11551
11552                            break;
11553                        case PAYMENT_METHODS.SOFT_CREDIT:
11554
11555                            transactionPayload.ThirdPartyPaymentInfo = {
11556                                "PlatformId": THIRD_PARTY_PAYMENT_PLATFORMS.DAFPay,
11557                                "PlatformToken": _dafPayWorkflowSessionId
11558                            };
11559
11560                            transactionPayload.PaymentAmount = thisApi.GetDiscountedAmount(); //no fees for DAFPay
11561                            transactionPayload.CoverFeesAmount = 0.00; //no fees for DAFPay
11562
11563                            break;
11564                        case PAYMENT_METHODS.IN_KIND:
11565
11566                            //NOTE: amount is used for value of asset (seamless)
11567                     
11568                            if (thisApi.InKindTypeClassId > 0) {
11569
11570                                _logger.Log('configure inkind details for class ' + thisApi.InKindTypeClassId);
11571                             
11572                                transactionPayload.InKindGiftInfo = {};
11573                                transactionPayload.InKindGiftInfo.Type_Id = thisApi.InKindTypeId;
11574                                transactionPayload.InKindGiftInfo.Quantity = 1;
11575                                transactionPayload.InKindGiftInfo.Description = null;
11576
11577                                switch (thisApi.InKindTypeClassId) {
11578                                    case IN_KIND_ASSET_CLASSES.AUTOMOBILES:
11579
11580                                        transactionPayload.InKindGiftInfo.VehicleInfo = {};
11581                                 
11582                                        transactionPayload.InKindGiftInfo.VehicleInfo.Year = thisApi.InKindVehicleYear;
11583                                        transactionPayload.InKindGiftInfo.VehicleInfo.Make = thisApi.InKindVehicleMake;
11584                                        transactionPayload.InKindGiftInfo.VehicleInfo.Mileage = thisApi.InKindVehicleMileage;
11585                                        transactionPayload.InKindGiftInfo.VehicleInfo.VIN = thisApi.InKindVehicleVin;
11586
11587                                        break;
11588                                    case IN_KIND_ASSET_CLASSES.STOCKS:
11589
11590                                        transactionPayload.InKindGiftInfo.StockBondInfo = {};
11591
11592                                        transactionPayload.InKindGiftInfo.StockBondInfo.Ticker = thisApi.InKindStockTicker;
11593                                        transactionPayload.InKindGiftInfo.StockBondInfo.Company = thisApi.InKindStockCompany;
11594
11595                                        break;
11596                                    case IN_KIND_ASSET_CLASSES.FOOD:
11597
11598                                        transactionPayload.InKindGiftInfo.FoodInfo = {};
11599
11600                                        transactionPayload.InKindGiftInfo.FoodInfo.WeightLbs = thisApi.InKindFoodWeight;
11601
11602                                        break;
11603                                }
11604                         
11605                            }
11606                            else
11607                                _logger.LogWarning('check out is set to in kind payment method but no class is present');
11608
11609                            break;
11610                        case PAYMENT_METHODS.CASH:
11611                            transactionPayload.IsCashPayment = true;
11612                            break;
11613                        case PAYMENT_METHODS.CHECK:
11614                            transactionPayload.IsCheckPayment = true;
11615                            break;
11616                        case PAYMENT_METHODS.NONE:
11617                            //nothing
11618                            break;
11619                    }
11620
11621                }
11622
11623                transactionPayload.Fields = [];
11624
11625                for (let x = 0; x < _allFieldApis.length; x++) {
11626            
11627                    let payloadField = {};
11628                 
11629                    //handle file upload fields
11630                    if (_allFieldApis[x].Configuration.Type.Id == FORM_FIELD_TYPES.FILE) {
11631
11632                        let fileVal = _allFieldApis[x].GetBase64Value();
11633                        if (!isBlank(fileVal))
11634                        { 
11635                            payloadField.Id = _allFieldApis[x].Configuration.Id;                        
11636                            payloadField.FileName = _allFieldApis[x].GetFileNameValue();
11637                            payloadField.FileDataBase64 = fileVal;
11638                        }
11639
11640                    }
11641                    else
11642                    {
11643
11644                        if (!isBlank(_allFieldApis[x].Value)) { 
11645                            payloadField.Id = _allFieldApis[x].Configuration.Id;
11646                            payloadField.Value = _allFieldApis[x].Value;
11647                        }
11648
11649                    }                                
11650
11651                    if(payloadField.Id > 0)
11652                        transactionPayload.Fields.push(payloadField);
11653                
11654                }                     
11655
11656                _clearHeaderMessageContent(); 
11657
11658                _trans_id = 0;
11659                _transAmount = 0;
11660
11661                _saveLastFormFillContactInfo();
11662
11663                //_logger.Log(JSON.stringify(transactionPayload));
11664
11665                const processFunc = function () {
11666
11667                    CE.APPS.ProcessWebFormTransaction({ "ajaxParams": transactionPayload },
11668                        function (result) {
11669
11670                            if (result.Success && (!result.PaymentAttempted || result.PaymentSuccessful)) {
11671
11672                                _trans_id = result.Transaction_Id;
11673                                _transAmount = result.PaymentAmount;
11674
11675                                _setSuccessView('', '', result.ResponsePageTitle, result.ResponseTopContent, result.ResponseBottomContent);
11676
11677                                //check for double the donation
11678                                if (!isBlank(WebFormContext.Form.DoubleTheDonationPublicKey) && window.doublethedonation && document.getElementById('dd-container') != null) {
11679
11680                                    document.getElementById('dd-container').show();
11681
11682                                    let ddNowFormatted = nowFormattedMMDDYYYYHHMMSSAMPM() + ' EST';
11683
11684                                    let ddPayLoad = {
11685                                        "360matchpro_public_key": WebFormContext.Form.DoubleTheDonationPublicKey,
11686                                        "partner_identifier": 'CHARI-O9zijvuqnBKeFA9o',
11687                                        "campaign": transactionPayload.Attribution.CustomCodes.Code1,
11688                                        "donation_identifier": result.Transaction_Id,
11689                                        "donation_amount": result.PaymentAmount,
11690                                        "donor_first_name": transactionPayload.Contact.PersonInfo.FirstName,
11691                                        "donor_last_name": transactionPayload.Contact.PersonInfo.LastName,
11692                                        "donor_email": transactionPayload.Contact.EmailAddress,
11693                                        "donor_address": {
11694                                            "zip": transactionPayload.Contact.AddressPostalCode,
11695                                            "city": transactionPayload.Contact.AddressCity,
11696                                            "state": transactionPayload.Contact.AddressState,
11697                                            "address1": transactionPayload.Contact.AddressStreet1,
11698                                            "address2": transactionPayload.Contact.AddressStreet1
11699                                        },
11700                                        "donation_datetime": ddNowFormatted
11701                                    };
11702
11703                                    //console.log('dd paylod: ' + JSON.stringify(ddPayLoad));
11704
11705                                    doublethedonation.integrations.core.register_donation(ddPayLoad);
11706
11707                                    doublethedonation.plugin.load_config();
11708                                    doublethedonation.plugin.set_donation_identifier(result.Transaction_Id);
11709                                    doublethedonation.plugin.set_donation_campaign("CharityEngine");
11710
11711                                    let domain = doublethedonation.integrations.core.strip_domain(transactionPayload.Contact.EmailAddress);
11712
11713                                    doublethedonation.plugin.email_domain(domain);
11714
11715                                }
11716
11717                                if (thisApi.OneClickCheckOutApi != null)
11718                                    thisApi.OneClickCheckOutApi.Enable(); //re-enable after payment is successful
11719
11720                                _resetExpiredRequestTokenAutoRetryCounter()
11721
11722                                _firePaymentCompletedEvent();
11723
11724                                //call reinitialize cas
11725                                if (result.Contact_Id > 0)
11726                                    reinitializeCas(result.Contact_Id,
11727                                        function () {
11728
11729                                            _logger.Log('post transaction cas reinitialization complete (' + result.Contact_Id + ')');
11730
11731                                        });
11732
11733                            }
11734                            else
11735                            {
11736                                
11737                                if (result.TokenExpired) {
11738                                    
11739                                    let autoResubmitCnt = App.LocalStorage.GetIntegerValueOrZero('webFormExpiredTokenResubmitCount');
11740
11741                                    _logger.Log('EXPIRED: ' + autoResubmitCnt);
11742
11743                                    if (autoResubmitCnt <= 0) {
11744
11745                                        autoResubmitCnt++;
11746
11747                                        App.LocalStorage.Save('webFormExpiredTokenResubmitCount', autoResubmitCnt);
11748
11749                                        _logger.Log('post expired token retry');
11750
11751                                        //retry once
11752                                        if (WebFormContext.Form.CaptchaRequired)
11753                                            App.ProcessAjaxWithCaptchaFailover(processFunc, thisApi.ContactPhoneNumber, thisApi.ContactEmailAddress, 'Complete Transaction'); // captchaFailedPhoneNumber,captchaFailedEmail,captchaFailedButtonText);
11754                                        else
11755                                            processFunc();
11756
11757                                    }
11758                                    else
11759                                    {
11760                                        _setErrorView('Error', 'Unable to process transaction. Please try again.', null, null, null);
11761                                        _resetExpiredRequestTokenAutoRetryCounter();
11762                                    }
11763
11764                                }
11765                                else if (result.Duplicate)
11766                                {
11767
11768                                    _setErrorView('This looks like a transaction you recently processed.', 'If this is not a duplicate, click the button below to process.', null, null, null); //we're not overriding content for now becuase it might cause confusion and undermine inspiration to try again (TBD)
11769
11770                                    let dupeGoButton = createClickInputButton('', '', '', 'Yes - Process Transaction', function () { _processTransaction(true); });
11771
11772                                    document.getElementById('headerDynamicMessageContent').addLineBreak();
11773                                    document.getElementById('headerDynamicMessageContent').appendChild(dupeGoButton);
11774
11775                                    _resetExpiredRequestTokenAutoRetryCounter();
11776
11777                                }
11778                                else if (result.OneClickFailure)
11779                                {
11780                                    _setErrorView('Error', 'Unable to process transaction. Please try again.', null, null, null); //we're not overriding content for now becuase it might cause confusion and undermine inspiration to try again (TBD) 
11781                                    _resetExpiredRequestTokenAutoRetryCounter();
11782                                }
11783                                else
11784                                { 
11785                                    _setErrorView('Error', result.ErrorMessage, result.ResponsePageTitle, null, null, null); //we're not overriding content for now becuase it might cause confusion and undermine inspiration to try again (TBD)
11786                                    _resetExpiredRequestTokenAutoRetryCounter();
11787                                }
11788
11789                                //handle one click error or decline (in both cases temporarily disable to force alternate payment method)
11790                                if (result.OneClickProfile_Id > 0 || result.OneClickFailure)
11791                                    thisApi.OneClickCheckOutApi.DisableTemporarily(); //special case to handle critical failure - disable for one hour and hide
11792
11793                            }
11794
11795                        },
11796                        function (error) {
11797                            _setErrorView('Error', 'Unable to process transaction. Please try again.', null, null, null); //we're not overriding content for now becuase it might cause confusion and undermine inspiration to try again (TBD)
11798                        });
11799
11800                };
11801
11802                if (WebFormContext.Form.CaptchaRequired)
11803                    App.ProcessAjaxWithCaptchaFailover(processFunc, thisApi.ContactPhoneNumber, thisApi.ContactEmailAddress, 'Complete Transaction'); // captchaFailedPhoneNumber,captchaFailedEmail,captchaFailedButtonText);
11804                else
11805                    processFunc();
11806                     
11807            }
11808
11809        }
11810
11811    };
11812
11813    const _handleSubmitClick = function () { thisApi.ProcessTransaction(); };
11814
11815    //Events
11816    //******
11817
11818    thisApi.RegisterFormInitializedCallBack = function (callBackFunction) {
11819        CallBackManager.add('formInitializedCallBack', callBackFunction, null);
11820    };
11821
11822    const _fireFormInitializedEvent = function () {
11823        CallBackManager.fire('formInitializedCallBack', {}, null);
11824    };
11825
11826    thisApi.RegisterFormReadyCallBack = function (callBackFunction) {
11827        CallBackManager.add('formReadyCallBack', callBackFunction, null);
11828    };
11829
11830    const _fireFormReadyEvent = function () {
11831        CallBackManager.fire('formReadyCallBack', {}, null);
11832    };
11833
11834    let _isThemeInitialized = false;
11835
11836    thisApi.RegisterThemeInitializedCallBack = function (callBackFunction) {
11837
11838        CallBackManager.add('form_' + thisApi.Form_Id + '_themeInitializedCallBack', callBackFunction, null);
11839
11840    };
11841
11842    //special event to be fired in by theme (dependency inejction)
11843    thisApi.FireThemeInitializedEvent = function () {
11844
11845        _isThemeInitialized = true;
11846
11847        let cbParam = {};
11848        cbParam.ThemeName = !isBlank(themeName) ? themeName : '';
11849        cbParam.ThemeVersion = !isBlank(themeVersion) ? themeVersion : 0;
11850
11851        CallBackManager.fire('form_' + thisApi.Form_Id + '_themeInitializedCallBack', cbParam, null);
11852
11853    };
11854
11855    thisApi.RegisterFormValidatedCallBack = function (callBackFunction) {
11856        CallBackManager.add('form_' + thisApi.Form_Id + '_validatedCallBack', callBackFunction, null);
11857    };
11858
11859    const _fireFormValidatedEvent = function () {
11860        CallBackManager.fire('form_' + thisApi.Form_Id + '_validatedCallBack', {}, null);
11861    };
11862
11863    thisApi.RegisterPaymentAmountChangedCallBack = function (callBackFunction) {
11864        CallBackManager.add('form_' + thisApi.Form_Id + '_paymentAmountChangedCallBack', callBackFunction, null);
11865    };
11866
11867    const _firePaymentAmountChangedEvent = function () {
11868
11869        let cbParams = {};
11870        cbParams.Amount = thisApi.GetTransactionAmount();
11871        cbParams.GenericAmount = thisApi.GenericAmountInputsAmount;
11872        cbParams.MembershipAmount = _getSelectedMembershipRate();
11873        cbParams.RegistrationAmount = thisApi.GetRegistrationAmount();
11874        cbParams.ProductsAmount = thisApi.GetProductsAmount();
11875
11876        CallBackManager.fire('form_' + thisApi.Form_Id + '_paymentAmountChangedCallBack', cbParams, null);
11877
11878    };
11879
11880    thisApi.RegisterRecurringPaymentAmountChangedCallBack = function (callBackFunction) {
11881        CallBackManager.add('form_' + thisApi.Form_Id + '_recurringAmountChangedCallBack', callBackFunction, null);
11882    };
11883
11884    const _fireRecurringAmountChangedEvent = function () {
11885
11886        let cbParam = {};
11887        cbParam.Amount = thisApi.RecurringPaymentAmountField.Value;
11888
11889        CallBackManager.fire('form_' + thisApi.Form_Id + '_recurringAmountChangedCallBack', cbParam, null);
11890
11891    };
11892
11893    thisApi.RegisterPaymentRecurringFrequencyChangedCallBack = function (callBackFunction) {
11894        CallBackManager.add('form_' + thisApi.Form_Id + '_paymentRecurringFrequencyChangedCallBack', callBackFunction, null);
11895    };
11896
11897    const _firePaymentRecurringFrequencyChangedEvent = function () {
11898
11899        let cbParam = {};
11900        cbParam.PaymentFrequency_Id = thisApi.RecurringPaymentFrequencyId;
11901
11902        CallBackManager.fire('form_' + thisApi.Form_Id + '_paymentRecurringFrequencyChangedCallBack', cbParam, null);
11903
11904    };
11905
11906    thisApi.RegisterPaymentCurrencyChangedCallBack = function (callBackFunction) {
11907        CallBackManager.add('form_' + thisApi.Form_Id + '_paymentCurrencyChangedCallBack', callBackFunction, null);
11908    };
11909
11910    const _firePaymentCurrencyChangedEvent = function () {
11911
11912        let cbParam = {};
11913        cbParam.PaymentCurrency_Id = thisApi.PaymentCurrencyField.Value;
11914
11915        CallBackManager.fire('form_' + thisApi.Form_Id + '_paymentCurrencyChangedCallBack', cbParam, null);
11916
11917    };
11918
11919    thisApi.RegisterPaymentMethodChangedCallBack = function (callBackFunction) {
11920        CallBackManager.add('form_' + thisApi.Form_Id + '_paymentMethodChangedCallBack', callBackFunction, null);
11921    };
11922
11923    const _firePaymentMethodChangedEvent = function () {
11924
11925        let cbParam = {};
11926        cbParam.PaymentMethod_Id = thisApi.PaymentMethodId;
11927
11928        CallBackManager.fire('form_' + thisApi.Form_Id + '_paymentMethodChangedCallBack', cbParam, null);
11929
11930    };
11931
11932    thisApi.RegisterPaymentCompletedCallBack = function (callBackFunction) {
11933        CallBackManager.add('form_' + thisApi.Form_Id + '_paymentCompletedCallBack', callBackFunction, null);
11934    };
11935
11936    const _firePaymentCompletedEvent = function () {
11937
11938        let cbParam = {};
11939        cbParam.Transaction_Id = thisApi.Transaction_Id;
11940        cbParam.PaymentAmount = thisApi.ProcessedPaymentAmount;
11941         
11942        CallBackManager.fire('form_' + thisApi.Form_Id + '_paymentCompletedCallBack', cbParam, null);
11943
11944    };
11945
11946    thisApi.RegisterPaymentMethodSectionShownCallBack = function (callBackFunction) {
11947        CallBackManager.add('form_' + thisApi.Form_Id + '_paymentMethodSectionShownCallBack', callBackFunction, null);
11948    };
11949
11950    const _firePaymentMethodSectionShownEvent = function () {
11951
11952        console.log('Fire Payment Method Shown Section Event');
11953
11954        let cbParam = {};
11955        cbParam.Section = thisApi.PaymentSection
11956
11957        CallBackManager.fire('form_' + thisApi.Form_Id + '_paymentMethodSectionShownCallBack', cbParam, null);
11958
11959    };
11960
11961    thisApi.RegisterAskStringAmountsChangedCallBack = function (callBackFunction) {
11962        CallBackManager.add('form_' + thisApi.Form_Id + '_askStringAmountsChangedCallBack', callBackFunction, null);
11963    };
11964
11965    const _fireAskStringAmountsChangedEvent = function () {
11966        CallBackManager.fire('form_' + thisApi.Form_Id + '_askStringAmountsChangedCallBack', {}, null);
11967    };
11968
11969    //Startup Helpers
11970    //***************
11971
11972    const _logCriticalCompatibilityIssue = function (message) {
11973
11974        _logger.Log('**********************************************');
11975        _logger.Log('CRITICAL CONFIGURATION ISSUE DETECTED');
11976        _logger.Log('configuration incompatible with web forms 2.0');
11977        _logger.Log('see details below and fix form settings');
11978        _logger.Log(message);
11979        _logger.Log('**********************************************');
11980
11981    };
11982
11983    const _criticalConfigurationMessages = [];
11984
11985    const _checkForCriticalConfigurationIssues = function () {
11986
11987        let retVal = false;
11988
11989        //we're allowing this
11990        //let mappedAmountFields = 0;
11991        //for (let x = 0; x < _allFieldApis.length; x++)
11992        //    if (_allFieldApis[x].Configuration.DataMapping.TransactionAmount) 
11993        //        mappedAmountFields++;                            
11994        //
11995        //if (mappedAmountFields > 1) {
11996        //    retVal = true;
11997        //    _criticalConfigurationMessages.push('more than one form field mapped to amount (only one field can be mapped to amount and ' + mappedAmountFields + ' were found)');
11998        //    _logCriticalCompatibilityIssue('more than one form field mapped to amount (only one field can be mapped to amount and ' + mappedAmountFields + ' were found)');
11999        //}
12000
12001        let mappedRecurringAmountFields = 0;
12002        for (let x = 0; x < _allFieldApis.length; x++)
12003            if (_allFieldApis[x].Configuration.DataMapping.RecurringAmount)
12004                mappedRecurringAmountFields++;             
12005
12006        if (mappedRecurringAmountFields > 1) {
12007            retVal = true;
12008            _criticalConfigurationMessages.push('more than one form field mapped to recurring amount (only one field can be mapped to recurring amount and ' + mappedRecurringAmountFields + ' were found)');
12009            _logCriticalCompatibilityIssue('more than one form field mapped to recurring amount (only one field can be mapped to recurring amount and ' + mappedRecurringAmountFields + ' were found)');
12010        }
12011
12012        let mappedRecurringFrequencyFields = 0;
12013        for (let x = 0; x < _allFieldApis.length; x++)
12014            if (_allFieldApis[x].Configuration.DataMapping.RecurringFrequency)
12015                mappedRecurringFrequencyFields++;             
12016
12017        if (mappedRecurringFrequencyFields > 1) {
12018            retVal = true;
12019            _criticalConfigurationMessages.push('more than one form field mapped to recurring frequency (only one field can be mapped to recurring frequency and ' + mappedRecurringFrequencyFields + ' were found)');
12020            _logCriticalCompatibilityIssue('more than one form field mapped to recurring frequency (only one field can be mapped to recurring frequency and ' + mappedRecurringFrequencyFields + ' were found)');
12021        }
12022
12023        let mappedCurrencyFields = 0;
12024        for (let x = 0; x < _allFieldApis.length; x++)
12025            if (_allFieldApis[x].Configuration.Type.Id == FORM_FIELD_TYPES.CURRENCY_SYSTEM)
12026                mappedCurrencyFields++;   
12027
12028        if (mappedCurrencyFields > 1) {
12029            retVal = true;            
12030            _criticalConfigurationMessages.push('more than one form field mapped to currency (only one field can be mapped to currency and ' + mappedCurrencyFields + ' were found)');
12031            _logCriticalCompatibilityIssue('more than one form field mapped to currency (only one field can be mapped to currency and ' + mappedCurrencyFields + ' were found)');
12032        }
12033
12034        if (thisApi.DafPayPaymentMethodAvailable && thisApi.FirstGenericAmountInputField == null) {
12035            retVal = true;
12036            _criticalConfigurationMessages.push('DAFPay configured but no generic amount input field on form (DAFPay requires at least one input field mapped to transaction amount)');
12037            _logCriticalCompatibilityIssue('DAFPay configured but no generic amount input field on form (DAFPay requires at least one input field mapped to transaction amount)');
12038        }
12039         
12040        let mappedFreeGiftFields = 0;
12041        for (let x = 0; x < _allFieldApis.length; x++)
12042            if (_allFieldApis[x].Configuration.Type.Id == FORM_FIELD_TYPES.FREE_GIFT_SELECTOR)
12043                mappedFreeGiftFields++;   
12044
12045        if (mappedFreeGiftFields > 1) {
12046            retVal = true;            
12047            _criticalConfigurationMessages.push('more than one form field configured as free gift selector (only one free gift selector field allowed and ' + mappedFreeGiftFields + ' were found)');
12048            _logCriticalCompatibilityIssue('more than one form field configured as free gift selector (only one free gift selector field allowed and ' + mappedFreeGiftFields + ' were found)');
12049        }
12050         
12051        return retVal;
12052
12053    };
12054
12055    const _wireUpFieldValueChangeDetectionEvent = function (fieldConfig, callBackFunction) {
12056
12057        if (fieldConfig != null && callBackFunction != null && typeof callBackFunction == 'function') {
12058
12059            let fieldElem = document.getElementById(fieldConfig.DomId);
12060            if (fieldElem != null) {
12061
12062                switch (fieldConfig.Type.Id) {
12063                    case FORM_FIELD_TYPES.RADIO_BUTTON:
12064                    case FORM_FIELD_TYPES.CHECK_BOX:
12065                        fieldElem.addEventListener('click', function () { callBackFunction(this); });
12066                        break;
12067                    case FORM_FIELD_TYPES.TEXT:
12068                        fieldElem.addEventListener('input', function () { callBackFunction(this); });
12069                        break;
12070                    case FORM_FIELD_TYPES.DROP_DOWN:
12071                        fieldElem.addEventListener('change', function () { callBackFunction(this); });
12072                        break;
12073                    default:
12074
12075                        switch (fieldConfig.SystemFieldRenderType_Id) {
12076                            case FORM_SYSTEM_FIELD_RENDER_TYPES.DropDown:
12077                                fieldElem.addEventListener('change', function () { callBackFunction(this); });
12078                                break;
12079                            case FORM_SYSTEM_FIELD_RENDER_TYPES.RadioButton:
12080                            case FORM_SYSTEM_FIELD_RENDER_TYPES.RadioButtonImages:
12081                            case FORM_SYSTEM_FIELD_RENDER_TYPES.CheckBox:
12082                                fieldElem.addEventListener('click', function () { callBackFunction(this); });
12083                                break;
12084                            case FORM_SYSTEM_FIELD_RENDER_TYPES.TextBox:
12085                                fieldElem.addEventListener('input', function () { callBackFunction(this); });
12086                                break;
12087                            default:
12088
12089                                let fldTp = tryGetInputFieldType(fieldElem);
12090                                switch (fldTp) {
12091                                    case FormInputTypes.TextBox:
12092                                    case FormInputTypes.Password:
12093                                    case FormInputTypes.TextArea:
12094                                        fieldElem.addEventListener('input', function () { callBackFunction(this); });
12095                                        break;
12096                                    case FormInputTypes.RadioButton:
12097                                    case FormInputTypes.CheckBox:
12098                                    case FormInputTypes.RadioButtonList:
12099                                    case FormInputTypes.CheckBoxList:
12100                                        fieldElem.addEventListener('click', function () { callBackFunction(this); });
12101                                        break;
12102                                    case FormInputTypes.DropDownList:
12103                                        fieldElem.addEventListener('change', function () { callBackFunction(this); });
12104                                        break;
12105                                    default:
12106                                        fieldElem.addEventListener('change', function () { callBackFunction(this); });
12107                                        break;
12108                                }
12109
12110                                break;
12111                        }
12112
12113                        break;
12114                }
12115
12116            }
12117
12118        }
12119
12120    };
12121     
12122    const _initializeOneClickCheckout = function (contact_id, paymentProfile_id, accountNumberLastFourWithDescription) {
12123
12124        let oneClickCompatPayMethAvail = _oneClickCompatiblePaymentMethodsAvailable();
12125        if (oneClickCompatPayMethAvail)
12126        {        
12127
12128            if (WebFormContext.Form.EnableOneClickCheckout) {
12129                 
12130                _logger.Log('init one-click check out for contact ' + contact_id + ' with profile ' + paymentProfile_id + ' (' + accountNumberLastFourWithDescription + ')');                        
12131
12132                let config = {};
12133                config.AutoInitialize = false;
12134                config.ParentElementDomId = thisApi.PaymentMethodField.SectionDomElement.id;
12135                config.AddAfterElementDomId = thisApi.PaymentMethodField.RowDomElement.id;
12136                config.Contact_Id = contact_id;
12137                config.PaymentProfile_Id = paymentProfile_id;
12138                config.PaymentProfileDescription = accountNumberLastFourWithDescription;
12139                config.FormRowCssClass = 'ce-formRows';
12140
12141                thisApi.OneClickCheckOutApi = getOneClickCheckOutApi(config);
12142
12143                thisApi.OneClickCheckOutApi.RegisterHideAllOptionsCallBack(function () {
12144
12145                    _tryHidePaymentMethodField(); 
12146
12147                    _hideAllPaymentFields();
12148
12149                });
12150                 
12151                thisApi.OneClickCheckOutApi.RegisterDefaultStateShownCallBack(function () { thisApi.HideSubmitButton(); });
12152                 
12153                thisApi.OneClickCheckOutApi.RegisterUseAccountSelectedCallBack(function () { thisApi.ShowSubmitButton(); });
12154                 
12155                thisApi.OneClickCheckOutApi.RegisterDontUseCallBack(function () {
12156
12157                    _tryShowPaymentMethodField();
12158
12159                    if (thisApi.PaymentMethodField.Value <= 0)
12160                        thisApi.PaymentMethodField.SelectFirstOption(); //set to first option
12161                    else
12162                        _handlePaymentMethodChanged();
12163
12164                });
12165                 
12166                thisApi.OneClickCheckOutApi.RegisterForgetInfoCallBack(function () {
12167
12168                    _tryShowPaymentMethodField();
12169
12170                    if (thisApi.PaymentMethodField.Value <= 0)
12171                        thisApi.PaymentMethodField.SelectFirstOption(); //set to first option
12172                    else
12173                        _handlePaymentMethodChanged();
12174
12175                });
12176
12177                thisApi.OneClickCheckOutApi.RegisterDisabledCallBack(function () {
12178
12179                    _tryShowPaymentMethodField();
12180
12181                    if (thisApi.PaymentMethodField.Value <= 0)
12182                        thisApi.PaymentMethodField.SelectFirstOption(); //set to first option
12183                    else
12184                        _handlePaymentMethodChanged();
12185
12186                });
12187
12188                thisApi.OneClickCheckOutApi.Initialize();
12189                 
12190            }
12191            else
12192                _logger.Log('one click checkout disabled');
12193                 
12194        }
12195        else
12196            _logger.Log('one click checkout disabled (no compatible payment method)');
12197
12198    };
12199
12200    const _refreshPaymentAmountSectionForFormUpdates = function () {
12201
12202        //_logger.Log('refresh payment amount sections for updates (' + _productizedPaymentAmountSectionInitialized + ')');
12203
12204        //todo: add any native logic here
12205
12206        //only for configured amount sections logic
12207        if (_productizedPaymentAmountSectionInitialized) {
12208
12209            let freq_id = thisApi.RecurringPaymentFrequencyId;
12210
12211            //handle out of the box settings (look for ask strings)
12212            //hide all ask strings
12213            _hideAllAmountAskStrings();
12214
12215            //show based on frequency
12216            let isAskStringShown = false;
12217            let asStrs = _getAmountAskStringBlocks();
12218            for (let x = 0; x < asStrs.length; x++)
12219                if (asStrs[x].tryGetIntegerAttribute('data-payment-frequency-id') == freq_id) {
12220                    asStrs[x].show();
12221                    isAskStringShown = true;
12222                    break;
12223                }
12224
12225            let askStringCont = document.getElementById('amountAskStringsContainer');
12226            if (askStringCont != null) {
12227
12228                askStringCont.vanish();
12229                if (isAskStringShown)
12230                    askStringCont.show();
12231
12232            }
12233
12234            //get textbox amount
12235            let amt = thisApi.FirstGenericAmountInputAmount;
12236
12237            //highlight radio if it matches text box amount
12238            let askInputs = _getShownAskBlockInputs();
12239            let anyAmountMatches = false;
12240            for (let x = 0; x < askInputs.length; x++)
12241                if (askInputs[x].value.toString() == amt.toString()) {
12242                    askInputs[x].checked = true;
12243                    anyAmountMatches = true;
12244                }
12245
12246            if (!anyAmountMatches) {
12247
12248                let selInput = _getShownAskBlockSelectedInput();
12249                if (selInput != null)
12250                    selInput.checked = false;
12251
12252            }
12253
12254        }
12255
12256    };
12257
12258    const _getShownAskStringBlock = function () {
12259
12260        let retVal = null;
12261
12262        if (_productizedPaymentAmountSectionInitialized) {
12263
12264            let freq_id = thisApi.RecurringPaymentFrequencyId;
12265
12266            let asStrs = _getAmountAskStringBlocks();
12267            for (let x = 0; x < asStrs.length; x++)
12268                if (asStrs[x].tryGetIntegerAttribute('data-payment-frequency-id') == freq_id) {
12269                    retVal = asStrs[x].querySelector('.ce-radioButtonLists');
12270                    break;
12271                }
12272
12273        }
12274
12275        return retVal;
12276
12277    };
12278
12279    const _getShownAskBlockInputs = function () {
12280
12281        let retVal = [];
12282
12283        let visBlock = _getShownAskStringBlock();
12284        if (visBlock != null)
12285            retVal = visBlock.querySelectorAll('input[type="radio"]');
12286
12287        return retVal;
12288
12289    };
12290
12291    const _getShownAskBlockSelectedInput = function () {
12292
12293        let retVal = null;
12294
12295        let visBlock = _getShownAskStringBlock();
12296        if (visBlock != null)
12297            retVal = visBlock.querySelector('input[type="radio"]:checked');
12298
12299        return retVal;
12300
12301    };
12302     
12303    const _simulateVisibleAskBlockClick = function () {
12304
12305        let visibleAskStringElem = _getShownAskStringBlock();
12306        if (visibleAskStringElem != null)
12307            _handleAskStringClick(visibleAskStringElem.id);
12308
12309    };
12310
12311    const _hideAllAmountAskStrings = function () {
12312
12313        let asStrs = _getAmountAskStringBlocks();
12314        for (let x = 0; x < asStrs.length; x++)
12315            asStrs[x].vanish();
12316
12317    };
12318
12319    const _getAmountAskStringBlocks = function () {
12320
12321        let retVal = [];
12322
12323        if (thisApi.FirstGenericAmountInputField != null) {
12324
12325            let paySecCfg = thisApi.FirstGenericAmountInputField.SectionDomElement;
12326            if (paySecCfg != null)
12327                retVal = paySecCfg.querySelectorAll('.ce-amountAskStringButtonContainers');
12328
12329        }
12330
12331        return retVal;
12332
12333    };
12334
12335    const _getAmountAskStringInputs = function () {
12336
12337        let retVal = [];
12338
12339        if (thisApi.FirstGenericAmountInputField != null) {
12340
12341            let paySecCfg = thisApi.FirstGenericAmountInputField.SectionDomElement;
12342            if (paySecCfg != null)
12343                retVal = paySecCfg.querySelectorAll('.ce-amountAskStringButtonContainers input[type="radio"]');
12344
12345        }
12346
12347        return retVal;
12348
12349    };
12350     
12351    const _addAskStringBlock = function (askStringButtonContainer, domId, freq_id, configOptions) {
12352
12353        if (configOptions != null && configOptions.length > 0) {
12354
12355            let askBlockContainer = createDomElement('div', domId + '_container', 'ce-amountAskStringButtonContainers');
12356            askBlockContainer.setAttribute('data-payment-frequency-id', freq_id);
12357
12358            let curSymb = _getPaymentCurrencySymbol();
12359
12360            let opts = [];
12361            for (let x = 0; x < configOptions.length; x++)
12362                opts.push(createMultiSelectInputOptionConfigJson(configOptions[x].Value, formatCurrency(configOptions[x].Value, false, curSymb), c
12362onfigOptions[x].IsDefault));
12363
12364            askBlockContainer.addRadioButtonList(domId, function () { _handleAskStringClick(domId); }, opts);
12365
12366            askStringButtonContainer.appendChild(askBlockContainer);
12367
12368            //Need to add currency-symbol attribute
12369            let rbList = askStringButtonContainer.querySelector('#' + domId);
12370
12371            if (rbList != null) {
12372                let rbLabels = rbList.querySelectorAll('label');
12373
12374                for (let i = 0; i < rbLabels.length; i++) {
12375                    rbLabels[i].setAttribute('data-currency-symbol', curSymb)
12376                }
12377            }
12378
12379        }
12380
12381    };
12382
12383    const _handleAskStringClick = function (domId) {        
12384
12385        //_logger.Log('ask string domId ' + domId);
12386
12387        let askStringElem = document.getElementById(domId);
12388
12389        //_logger.Log('ask string ' + askStringElem.tagName + ' clicked');
12390        //_logger.Log('ask string ' + askStringElem.id + ' clicked');
12391
12392        let selectedAmtValue = getMultiSelectInputSelectedDecimalValue(askStringElem);
12393
12394        _logger.Log('ask string click set payment amount to ' + selectedAmtValue);
12395
12396        thisApi.FirstGenericAmountInputField.SetValue(selectedAmtValue);
12397
12398    };
12399
12400    const _showCoverFeesWidget = function () {
12401
12402        if (document.getElementById('coverFeesDisplayRow') != null) document.getElementById('coverFeesDisplayRow').show();
12403
12404    };
12405
12406    const _hideCoverFeesWidget = function () {
12407
12408        _logger.Log('hide cover fees widget');
12409
12410        if (document.getElementById('coverFeesDisplayRow') != null) document.getElementById('coverFeesDisplayRow').vanish();
12411        _hideCoverFeesMessageRows(); //only hide when hiding whole widget
12412
12413    };
12414
12415    const _refreshCoverFeesWidget = function () {
12416
12417        if (document.getElementById('coverFeesDisplayAmount') != null) {
12418
12419            let amt = thisApi.GetDiscountedAmount();
12420            if (amt > 0) {
12421                
12422                let feesFormatted = _getFeesFormatted();
12423
12424                document.getElementById('coverFeesDisplayAmount').innerText = '+ ' + feesFormatted + ' processing fee';
12425
12426                let possibleFees = _calculateSelectedPaymentMethodfees();
12427                if (possibleFees > 0)
12428                    _showCoverFeesWidget();
12429                else
12430                    _hideCoverFeesWidget();
12431
12432            }
12433            else
12434                _hideCoverFeesWidget();
12435
12436        }
12437
12438    };
12439
12440    const _displayCoverFeesMessage = function () {
12441
12442        if (document.getElementById('coverFeesMessageRow') != null) document.getElementById('coverFeesMessageRow').show();
12443        if (document.getElementById('coverFeesCheckBoxRow') != null) document.getElementById('coverFeesCheckBoxRow').show();
12444
12445        if (document.getElementById('coverFeesDisplayEditLink') != null) document.getElementById('coverFeesDisplayEditLink').innerText = 'done';
12446
12447        _updateFormAfterAmountImpactingChange(); //update cost amount
12448
12449    };
12450
12451    const _hideCoverFeesMessageRows = function () {
12452
12453        if (document.getElementById('coverFeesMessageRow') != null) document.getElementById('coverFeesMessageRow').vanish();
12454        if (document.getElementById('coverFeesCheckBoxRow') != null) document.getElementById('coverFeesCheckBoxRow').vanish();
12455
12456    };
12457
12458    const _hideCoverFeesMessage = function () {
12459
12460        _hideCoverFeesMessageRows();
12461
12462        if (document.getElementById('coverFeesDisplayEditLink') != null) document.getElementById('coverFeesDisplayEditLink').innerText = 'edit';
12463
12464        _updateFormAfterAmountImpactingChange(); //update cost amount
12465
12466    };
12467
12468    const _handleCoverProcessingCostsEditClick = function () {
12469
12470        if (document.getElementById('coverFeesDisplayEditLink') != null) {
12471
12472            if (document.getElementById('coverFeesDisplayEditLink').innerText == 'edit')
12473                _displayCoverFeesMessage();
12474            else
12475                _hideCoverFeesMessage();
12476
12477        }
12478
12479    };
12480
12481    const _handleToggleCoverProcessingCost = function () {
12482
12483        _logger.Log('cover processing fees toggle clicked');
12484
12485        _hideCoverFeesMessage();
12486
12487    };
12488
12489    let _coverFeesInitialized = false;
12490
12491    const _addCoverProcessingCostBlock = function (sectionElem) {
12492         
12493        if (!_coverFeesInitialized && sectionElem != null) {
12494
12495            //check for config
12496            if (!WebFormContext.Form.EnableDonorPayProcessingFee) {
12497                _logger.Log('cover processing fees disabled on form (abort initialization)');
12498                return;
12499            }
12500
12501            _coverFeesInitialized = true;
12502
12503            //add top row with toggle
12504            let coverFeesTopRowAmtToggle = _createFormRow('coverFeesDisplayRow', 'ce-coverFeesDisplayRows');
12505
12506            coverFeesTopRowAmtToggle.addText('+ $0.00 processing fee', 'coverFeesDisplayAmount');
12507
12508            coverFeesTopRowAmtToggle.addHtmlClickLink('edit', function () { _handleCoverProcessingCostsEditClick(); }, 'coverFeesDisplayEditLink');
12509
12510            sectionElem.appendChild(coverFeesTopRowAmtToggle);
12511
12512            //next add middle content row
12513            _addRowWithText(sectionElem, 'coverFeesMessageRow', 'ce-coverFeesMessageRows', 'Payment processors take a cut of each transaction. You have the option to cover these fees so 100% of your gift can go to the cause you care about.', 'coverFeesMessage');
12514
12515            //bottom row added first
12516            _addRowWithSingleCheckBoxOption(sectionElem, 'coverFeesCheckBoxRow', 'ce-coverFeesCheckBoxes', 'coverFeesCheckBox', true, 'I want to cover costs', true, function () { _handleToggleCoverProcessingCost(); });
12517
12518            _handleToggleCoverProcessingCost();
12519
12520        }
12521
12522    };
12523
12524    const _getAmountSectionDomElement = function () {
12525
12526        let retVal = null;
12527
12528        if(thisApi.FirstGenericAmountInputField != null)
12529            retVal = thisApi.FirstGenericAmountInputField.SectionDomElement;
12530
12531        if (retVal == null && thisApi.RecurringPaymentAmountField != null)
12532            retVal = thisApi.RecurringPaymentAmountField.SectionDomElement;
12533
12534        return retVal;
12535
12536    };
12537
12538    const _tryHideInKindTypeDetailFields = function () {
12539
12540        let ikFlds = document.querySelectorAll('.ce-inKindDetailRows');
12541        for (let x = 0; x < ikFlds.length; x++)
12542            ikFlds[x].vanish();
12543
12544    };
12545     
12546    const _addInKinDetailTextInputRow = function (sectionElement, domId, classes, inputDomId, label, inputClasses, placeHolder, class_id) {
12547
12548        _addRowWithSingleTextInput(sectionElement, domId, classes, inputDomId, label, inputClasses, placeHolder);
12549        document.getElementById(domId).setAttribute('data-in-kind-class-id', class_id);
12550
12551    };
12552
12553    const _tryShowInKindTypeDetailsByClass = function (class_id) {
12554
12555        _tryHideInKindTypeDetailFields();
12556
12557        let ikFlds = document.querySelectorAll('.ce-inKindDetailRows');
12558        for (let x = 0; x < ikFlds.length; x++)
12559            if(ikFlds[x].tryGetIntegerAttribute('data-in-kind-class-id') == class_id)
12560            ikFlds[x].show();
12561
12562    };
12563
12564    const _refreshInKindFieldsByTypeSelected = function () {
12565
12566        let ikClassId = thisApi.InKindTypeClassId;
12567
12568        _tryShowInKindTypeDetailsByClass(ikClassId);
12569
12570    };
12571
12572    const _handleInKindTypeChanged = function () {
12573
12574        _refreshInKindFieldsByTypeSelected();
12575
12576        //add event call back
12577
12578    };
12579
12580    let _productizedPaymentAmountSectionInitialized = false;
12581
12582    const _initializePaymentAmountSection = function () {
12583
12584        //native field event bindings (moved to generic amount wire ups for multiple)
12585        //if (thisApi.Payment_XXX_AmountField != null)
12586        //    _wireUpFieldValueChangeDetectionEvent(thisApi.Payment_XXX_AmountField.Configuration, _handle_XXX_PaymentAmountChanged);
12587
12588        if (thisApi.RecurringPaymentFrequencyField != null)
12589            bindMultiSelectInputChangedEvent(thisApi.RecurringPaymentFrequencyField.DomElement, function () { _handlePaymentFrequencyChanged(); });
12590
12591        if (thisApi.PaymentCurrencyField != null)
12592            thisApi.PaymentCurrencyField.DomElement.addEventListener('change', function () { _handleCurrencyChanged(); });
12593
12594        //custom configuration below this point after native
12595        const paySecCfg = _getDonationAmountSectionConfig();
12596        if (paySecCfg != null) {
12597
12598            _logger.Log('configure payment amount section (configuration present)');
12599             
12600            _productizedPaymentAmountSectionInitialized = true;
12601
12602            let hasAskStrings = ((paySecCfg.OneTimeAmountOptions != null && paySecCfg.OneTimeAmountOptions.length > 0)
12603                || (paySecCfg.DailyAmountOptions != null && paySecCfg.DailyAmountOptions.length > 0)
12604                || (paySecCfg.WeeklyAmountOptions != null && paySecCfg.WeeklyAmountOptions.length > 0)
12605                || (paySecCfg.BiMonthlyAmountOptions != null && paySecCfg.BiMonthlyAmountOptions.length > 0)
12606                || (paySecCfg.MonthlyAmountOptions != null && paySecCfg.MonthlyAmountOptions.length > 0)
12607                || (paySecCfg.QuarterlyAmountOptions != null && paySecCfg.QuarterlyAmountOptions.length > 0)
12608                || (paySecCfg.BiAnnualAmountOptions != null && paySecCfg.BiAnnualAmountOptions.length > 0)
12609                || (paySecCfg.AnnualAmountOptions != null && paySecCfg.AnnualAmountOptions.length > 0));
12610            if (hasAskStrings) {
12611
12612                if (thisApi.FirstGenericAmountInputField.RowDomElement != null)
12613                { 
12614
12615                    _logger.Log('configure ask string blocks');
12616
12617                    let askStringButtonContainer = _createFormRow('amountAskStringsContainer', 'ce-amountAskStringContainers'); 
12618
12619                    //consider moving this to field options instead of specialton - i.e., there are 0 to 8 radio button lists that have field options for the amounts - benefit: if section is converted to custom, user can show/hide radio sets based on separate logic
12620                    _addAskStringBlock(askStringButtonContainer, 'oneTimeAskStrings', PaymentFrequencies.OneTime, paySecCfg.OneTimeAmountOptions);
12621                    _addAskStringBlock(askStringButtonContainer, 'dailyAskStrings', PaymentFrequencies.Daily, paySecCfg.DailyAmountOptions);
12622                    _addAskStringBlock(askStringButtonContainer, 'weeklyAskStrings', PaymentFrequencies.Weekly, paySecCfg.WeeklyAmountOptions);
12623                    _addAskStringBlock(askStringButtonContainer, 'bimonthlyAskStrings', PaymentFrequencies.BiMonthly, paySecCfg.BiMonthlyAmountOptions);
12624                    _addAskStringBlock(askStringButtonContainer, 'monthlyAskStrings', PaymentFrequencies.Monthly, paySecCfg.MonthlyAmountOptions);
12625                    _addAskStringBlock(askStringButtonContainer, 'quarterlyAskStrings', PaymentFrequencies.Quarterly, paySecCfg.QuarterlyAmountOptions);
12626                    _addAskStringBlock(askStringButtonContainer, 'biannualAskStrings', PaymentFrequencies.BiAnnually, paySecCfg.BiAnnualAmountOptions);
12627                    _addAskStringBlock(askStringButtonContainer, 'annualAskStrings', PaymentFrequencies.Annually, paySecCfg.AnnualAmountOptions);
12628                 
12629                    thisApi.FirstGenericAmountInputField.RowDomElement.insertBeforeElement(askStringButtonContainer);
12630
12631                }
12632                else
12633                    _logger.Log('unable to configure ask string blocks (cannot find donation input field)');
12634
12635            }
12636                              
12637        }
12638        else
12639            _logger.Log('unable to configure amount section type (cannot find amount section config)');
12640
12641        const paySecElem = _getAmountSectionDomElement(); 
12642        if (paySecElem != null) {
12643
12644            //add in-kind fields
12645            let inKindAvailOnForm = _inKindPaymentMethodAvailable();
12646            if (inKindAvailOnForm && thisApi.InKindTypeField != null) {
12647
12648                _logger.Log('initialize inkind fields (payment method available)');
12649
12650                //native field event bindings 
12651                _wireUpFieldValueChangeDetectionEvent(thisApi.InKindTypeField.Configuration, _handleInKindTypeChanged);
12652
12653                _addInKinDetailTextInputRow(paySecElem, 'inKindVehicleYearRow', 'ce-inKindDetailRows', 'inKindVehicleYear', 'Year', 'ce-inKindDetails', 'Vehicle Year', IN_KIND_ASSET_CLASSES.AUTOMOBILES);                
12654                _addInKinDetailTextInputRow(paySecElem, 'inKindVehicleMakeRow', 'ce-inKindDetailRows', 'inKindVehicleMake', 'Make/Model', 'ce-inKindDetails', 'Vehicle Make/Model', IN_KIND_ASSET_CLASSES.AUTOMOBILES);                
12655                _addInKinDetailTextInputRow(paySecElem, 'inKindeVehicleMileageRow', 'ce-inKindDetailRows', 'inKindeVehicleMileage', 'Mileage', 'ce-inKindDetails', 'Vehicle Mileage', IN_KIND_ASSET_CLASSES.AUTOMOBILES);                
12656                _addInKinDetailTextInputRow(paySecElem, 'inKindVinNumberRow', 'ce-inKindDetailRows', 'inKindVinNumber', 'VIN #', 'ce-inKindDetails', 'VIN Number', IN_KIND_ASSET_CLASSES.AUTOMOBILES);                
12657
12658                _addInKinDetailTextInputRow(paySecElem, 'inKindStockTickerRow', 'ce-inKindDetailRows', 'inKindStockTicker', 'Stock Ticker', 'ce-inKindDetails', 'Ticker', IN_KIND_ASSET_CLASSES.STOCKS);                
12659                _addInKinDetailTextInputRow(paySecElem, 'inKindStockCompanyRow', 'ce-inKindDetailRows', 'inKindStockCompany', 'Company', 'ce-inKindDetails', 'Company', IN_KIND_ASSET_CLASSES.STOCKS);                                
12660
12661                _addInKinDetailTextInputRow(paySecElem, 'inKindFoodWeightRow', 'ce-inKindDetailRows', 'inKindFoodWeight', 'Weight (lbs)', 'ce-inKindDetails', 'Weight (lbs)', IN_KIND_ASSET_CLASSES.FOOD);                
12662
12663                _refreshInKindFieldsByTypeSelected();
12664
12665            }
12666
12667            _addCoverProcessingCostBlock(paySecElem);
12668
12669        }
12670        else
12671            _logger.Log('unable to configure native amount section functionality (cannot find section dom element)');
12672
12673    };
12674     
12675    const _reinitiailzeAskAmountBlock = function (askBlock, startAmount, startAmountMultiplier) {
12676
12677        if (askBlock != null) {
12678
12679            let curSymb = _getPaymentCurrencySymbol();
12680
12681            let rbs = getRadioButtonOptionElements(askBlock);
12682
12683            let newApi = getEndUserSuggestedDonationAskAmountsApi(startAmount, rbs.length, startAmountMultiplier);
12684            let opts = newApi.GenerateAmountsFromSeed();
12685
12686            for (let x = 0; x < opts.length; x++) {
12687
12688                rbs[x].value = opts[x];
12689
12690                let lbl = getLabelForField(rbs[x], askBlock);
12691                lbl.innerHTML = formatCurrency(opts[x], false, curSymb);
12692
12693                lbl.setAttribute('data-currency-symbol', curSymb);
12694
12695            }
12696
12697        }
12698
12699    };
12700
12701    const _reinitializeAskAmounts = function (startAmount, startAmountMultiplier) {
12702
12703        _logger.Log('reinitiailze ask string with start amount ' + startAmount);
12704
12705        let opts = _getAmountAskStringBlocks();
12706
12707        if (opts.length > 0) {
12708
12709            let seedAmount = 50;
12710
12711            if (startAmount > 0)
12712                seedAmount = roundEndUserSuggestedDonationAmount(startAmount);
12713
12714            //Never start with more than $500 on donation form
12715            if (seedAmount > 500)
12716                seedAmount = 500;
12717
12718            _reinitiailzeAskAmountBlock(document.getElementById('oneTimeAskStrings'), seedAmount, startAmountMultiplier);
12719
12720            let dailySeed = roundEndUserSuggestedDonationAmount(seedAmount * 0.1);
12721            _reinitiailzeAskAmountBlock(document.getElementById('dailyAskStrings'), dailySeed, startAmountMultiplier);
12722
12723            let weeklySeed = roundEndUserSuggestedDonationAmount(seedAmount * 0.2);
12724            _reinitiailzeAskAmountBlock(document.getElementById('weeklyAskStrings'), weeklySeed, startAmountMultiplier);
12725
12726            let bimonthlySeed = roundEndUserSuggestedDonationAmount(seedAmount * 0.3);
12727            _reinitiailzeAskAmountBlock(document.getElementById('bimonthlyAskStrings'), bimonthlySeed, startAmountMultiplier);
12728
12729            let monthlySeed = roundEndUserSuggestedDonationAmount(seedAmount * 0.4);
12730            _reinitiailzeAskAmountBlock(document.getElementById('monthlyAskStrings'), monthlySeed, startAmountMultiplier);
12731
12732            let quarterlySeed = roundEndUserSuggestedDonationAmount(seedAmount * 0.6);
12733            _reinitiailzeAskAmountBlock(document.getElementById('quarterlyAskStrings'), quarterlySeed, startAmountMultiplier);
12734
12735            let biannualSeed = roundEndUserSuggestedDonationAmount(seedAmount * 0.75);
12736            _reinitiailzeAskAmountBlock(document.getElementById('biannualAskStrings'), bi
12736annualSeed, startAmountMultiplier);
12737
12738            let annualSeed = roundEndUserSuggestedDonationAmount(seedAmount * 0.9);
12739            _reinitiailzeAskAmountBlock(document.getElementById('annualAskStrings'), annualSeed, startAmountMultiplier);
12740
12741            if (!_isUrlAmountProvided)
12742                _simulateVisibleAskBlockClick();
12743            else
12744                _logger.Log('skip startup simulate ask click because of amount url override');
12745
12746        }
12747
12748        _fireAskStringAmountsChangedEvent();
12749
12750    };    
12751
12752    const _handleContactTypeChanged = function () {
12753
12754        _tryHideField(thisApi.OrganizationNameField);
12755
12756        let cntTpId = thisApi.ContactTypeId;
12757
12758        _logger.Log('contact type changed to ' + cntTpId);
12759
12760        if (cntTpId > 0) {
12761
12762            switch (cntTpId) {
12763                case CONTACT_TYPES.Person:
12764                    //no changd
12765                    break;
12766                case CONTACT_TYPES.Organization:
12767                    //_logger.Log('try show ' + thisApi.OrganizationNameFieldElement);
12768                    _tryShowField(thisApi.OrganizationNameField);
12769                    break;
12770            }
12771
12772        }
12773
12774    };
12775
12776    let _contactInforSectionAddressApi = null;
12777
12778    const _initializeContactInfoSection = function () {
12779
12780        if (thisApi.ContactAddressStreet1Field != null)
12781            _contactInforSectionAddressApi = getFormAddressApi({ AutoInitialize: false, Required: false, FormOrContainerDomId: thisApi.FormDomId, Street1FieldDomId: thisApi.ContactAddressStreet1Field.DomElement.id, Street2FieldDomId: thisApi.ContactAddressStreet2Field.DomElement.id, CityFieldDomId: thisApi.ContactAddressCityField.DomElement.id, StateProvinceFieldDomId: thisApi.ContactAddressStateProvinceField.DomElement.id, PostalCodeFieldDomId: thisApi.ContactAddressPostalCodeField.DomElement.id, CountryFieldDomId: thisApi.ContactAddressCountryField.DomElement.id, ApplyStyles: false, AddBlankStateOption: true });
12782             
12783        //wire up contact type if present
12784        if (thisApi.ContactTypeField != null)
12785            bindMultiSelectInputChangedEvent(thisApi.ContactTypeField.DomElement, function () { _handleContactTypeChanged(); });
12786
12787        _handleContactTypeChanged();         
12788        _contactInforSectionAddressApi.Initialize();
12789
12790    };
12791
12792    const _handleToggleTributeClicked = function () {
12793
12794        let tribSecConfig = _tryGetTributeInfoSectionConfiguration();
12795        if (tribSecConfig != null) {
12796             
12797            let tcb = document.getElementById('dedicateGiftCheckBox').getValue();
12798            if (tcb)
12799                _tryShowSection(tribSecConfig);
12800            else
12801                _tryHideSection(tribSecConfig);
12802
12803        }
12804
12805    };
12806
12807    const _tryHideAllTributeContactInfoFields = function () {
12808
12809        for (let x = 0; x < _allFieldApis.length; x++)
12810            if (_allFieldApis[x].IsTributeRecipientContactInformation || _allFieldApis[x].IsTributeCardConfiguration)
12811                _allFieldApis[x].Hide();
12812
12813        //special field
12814        //tribute single line
12815        if (document.getElementById('tributeAddressSingleLineEntryRow') != null)
12816            document.getElementById('tributeAddressSingleLineEntryRow').vanish();
12817
12818        //special field
12819        if (document.getElementById('tributeEmailMessageTypeToggleContainer') != null)
12820            document.getElementById('tributeEmailMessageTypeToggleContainer').vanish();
12821
12822    };
12823
12824    const _tryGetSelectedTributeRecipientType = function () {
12825
12826        let retVal = '';
12827
12828        let trbRecipRbl = document.getElementById('tributeRecipientToggle');
12829        if (trbRecipRbl != null) 
12830            retVal = trbRecipRbl.getValue();
12831
12832        return retVal;
12833
12834    };
12835
12836    const _handleTributeRecipientTypeClick = function () {        
12837
12838        _tryHideAllTributeContactInfoFields();
12839
12840        let selVal = _tryGetSelectedTributeRecipientType();
12841        _logger.Log('tribute recipient type changed to: ' + selVal);
12842        switch (selVal) {
12843            case 'mail':
12844
12845                //special field
12846                //tribute single line
12847                if (document.getElementById('tributeAddressSingleLineEntryRow') != null) {
12848                    document.getElementById('tributeAddressSingleLineEntryRow').show();
12849                    _hideAllTributeAddressFields();
12850                }
12851                else { 
12852                    _tryShowField(thisApi.TributeRecipientAddressStreet1Field);
12853                    _tryShowField(thisApi.TributeRecipientAddressStreet2Field);
12854                    _tryShowField(thisApi.TributeRecipientAddressCityField);
12855                    _tryShowField(thisApi.TributeRecipientAddressStateField);
12856                    _tryShowField(thisApi.TributeRecipientAddressPostalCodeField);
12857                    _tryShowField(thisApi.TributeRecipientAddressCountryField);
12858                }
12859
12860                _tryShowField(thisApi.PrintCardCountField);
12861                    
12862                _togglePrintCardsVisibilityBasedOnCountSelected();
12863
12864                break;
12865            case 'email':
12866
12867                _tryShowField(thisApi.TributeRecipientEmailAddressField);
12868
12869                if (document.getElementById('tributeEmailMessageTypeToggleContainer') != null)
12870                    document.getElementById('tributeEmailMessageTypeToggleContainer').show();
12871                    
12872                _toggleTributeEcardVisibilityBasedOnConfiguration();
12873
12874                break;
12875            case 'phone':
12876                _tryShowField(thisApi.TributeRecipientPhoneNumberField);
12877                break;
12878        } 
12879
12880    };
12881
12882    const _isTributeEcardOptionSelected = function () {
12883
12884        let messTp = '';
12885        let messTpRbl = document.getElementById('tributeEmailMessageTypeToggle');
12886        if (messTpRbl != null) 
12887            messTp = messTpRbl.getValue();
12888
12889        //_logger.Log('is e-card selected val: ' + messTp);
12890
12891        return (messTp == 'card');
12892
12893    };
12894
12895    const _toggleTributeEcardVisibilityBasedOnConfiguration = function () {
12896
12897        _tryHideField(thisApi.ECardField);
12898
12899        let selVal = _tryGetSelectedTributeRecipientType();
12900        //_logger.Log('toggle tribute e-card visibility: ' + selVal);        
12901        if (selVal == 'email' && _isTributeEcardOptionSelected())
12902            _tryShowFieldIfHasOptions(thisApi.ECardField);
12903
12904    };
12905
12906    const _handleTributeEmailMessageTypeClick = function () { _toggleTributeEcardVisibilityBasedOnConfiguration(); };
12907
12908    const _togglePrintCardsVisibilityBasedOnCountSelected = function () {
12909
12910        _tryHideField(thisApi.PrintCardField);
12911
12912        let selVal = _tryGetSelectedTributeRecipientType();
12913        if (selVal == 'mail' && thisApi.PrintCardCount > 0)
12914            _tryShowFieldIfHasOptions(thisApi.PrintCardField);
12915
12916    };
12917
12918    const _handlePrintCardCountChanged = function () { _togglePrintCardsVisibilityBasedOnCountSelected();  };
12919
12920    const _tryGetTributeInfoSectionConfiguration = function () {
12921
12922        let retVal = null;
12923
12924        if (thisApi.TributeTypeField != null || thisApi.TributeNameField != null) {
12925
12926            if (thisApi.TributeTypeField != null)
12927                retVal = thisApi.TributeTypeField.SectionConfiguration;
12928            else
12929                retVal = thisApi.TributeNameField.SectionConfiguration;
12930
12931        }
12932
12933        return retVal;
12934
12935    };
12936
12937    let _tributeRecipientInforSectionAddressApi = null;
12938
12939    const _initializeTributeSection = function () {
12940
12941        if (thisApi.TributeRecipientAddressStreet1Field != null)
12942            _tributeRecipientInforSectionAddressApi = getFormAddressApi({ AutoInitialize: false, Required: false, FormOrContainerDomId: thisApi.FormDomId, Street1FieldDomId: thisApi.TributeRecipientAddressStreet1Field.DomElement.id, Street2FieldDomId: thisApi.TributeRecipientAddressStreet2Field.DomElement.id, CityFieldDomId: thisApi.TributeRecipientAddressCityField.DomElement.id, StateProvinceFieldDomId: thisApi.TributeRecipientAddressStateField.DomElement.id, PostalCodeFieldDomId: thisApi.TributeRecipientAddressPostalCodeField.DomElement.id, CountryFieldDomId: thisApi.TributeRecipientAddressCountryField.DomElement.id, ApplyStyles: false, AddBlankStateOption: true });
12943
12944        //NOTE: tribute dependent on amount section - we're putting the checkbox there for now.     
12945        let tribSecConfig = _tryGetTributeInfoSectionConfiguration();
12946        if (tribSecConfig != null) {
12947
12948            _logger.Log('begin initialize tribute section (tribute type field present)');
12949
12950            let paymentSectionElem = _getAmountSectionDomElement(); 
12951
12952            if (paymentSectionElem != null) {
12953
12954                _addRowWithSingleCheckBoxOption(paymentSectionElem, 'dedicateGiftCheckBoxContainer', '', 'dedicateGiftCheckBox', true, 'Dedicate this gift', false, function () { _handleToggleTributeClicked(); });
12955
12956                _handleToggleTributeClicked();
12957
12958            }
12959            else
12960                _logger.Log('unable to initialize tribute section becuase there is no amount section');
12961
12962            //toggle should be assumed
12963            let firstTributeContactInfField = _getFirstTributeRecipientContactInfoField();
12964            if (firstTributeContactInfField != null) {
12965
12966                let enableEmail = true;
12967                let emailBtnLabel = 'Email';
12968                let enablePhone = true;
12969                let phoneBtnLabel = 'Phone Call';
12970                let enableLetter = true;
12971                let letterBtnLabel = 'Letter';
12972
12973                let noAnnouncementBtnLbl = 'No Announcement';
12974
12975                let tribOobCfg = thisApi.TributeSectionConfig;
12976                if (tribOobCfg != null) {
12977
12978                    enableEmail = tribOobCfg.EnableEmailAcknowledgement;
12979                    if (!isBlank(tribOobCfg.EmailAcknowledgementLabel))
12980                        emailBtnLabel = tribOobCfg.EmailAcknowledgementLabel;
12981
12982                    enablePhone = false; // not implemented tribOobCfg.;
12983
12984                    enableLetter = tribOobCfg.EnableMailAcknowledgement;
12985                    if (!isBlank(tribOobCfg.MailAcknowledgementLabel))
12986                        letterBtnLabel = tribOobCfg.MailAcknowledgementLabel;
12987
12988                    noAnnouncementBtnLbl = ''; //allow removal via blank label
12989                    if (!isBlank(tribOobCfg.NoAcknowledgementLabel))
12990                        noAnnouncementBtnLbl = tribOobCfg.NoAcknowledgementLabel;
12991
12992                }
12993
12994                let optCnt = 0;
12995                let optsCsv = '';
12996                let defOptVal = '';
12997
12998                if (enableEmail && thisApi.TributeRecipientEmailAddressField != null) {
12999
13000                    optsCsv = optsCsv.delimitedListAdd('email=' + emailBtnLabel);
13001                    defOptVal = 'email';
13002                    optCnt++;
13003
13004                    //wire up experience for e-card what type of message would you like:  gift notification?  e-card?
13005                    if (thisApi.ECardField != null && thisApi.ECardField.HasOptions) {
13006                         
13007                        let tributeEmailMessageTypeToggleRow = _createRowWithRadioButtonList('tributeEmailMessageTypeToggleContainer', 'ce-tributeRecipientEmailMessageTypeToggleContainers', 'tributeEmailMessageTypeToggle', 'What type of message would you like to send?', 'general=gift notification,card=e-card', 'general', function () { _handleTributeEmailMessageTypeClick(); });
13008
13009                        firstTributeContactInfField.SectionDomElement.insertAfterElement(tributeEmailMessageTypeToggleRow, thisApi.TributeRecipientEmailAddressField.RowDomElement);
13010
13011                    }
13012
13013                }
13014
13015                if (enablePhone && thisApi.TributeRecipientPhoneNumberField != null) {
13016                    optsCsv = optsCsv.delimitedListAdd('phone=' + phoneBtnLabel);
13017                    defOptVal = 'phone';
13018                    optCnt++;
13019                }
13020
13021                if (enableLetter && thisApi.TributeRecipientAddressStreet1Field != null) {
13022                    optsCsv = optsCsv.delimitedListAdd('mail=' + letterBtnLabel);
13023                    defOptVal = 'mail';
13024                    optCnt++;
13025                }
13026
13027                //don't show if none
13028                if (optCnt > 0) {
13029
13030                    if (!isBlank(noAnnouncementBtnLbl)) {
13031                        optsCsv = optsCsv.delimitedListAdd('none=' + noAnnouncementBtnLbl);
13032                        defOptVal = 'none';
13033                        optCnt++;
13034                    }
13035
13036                    let tributeRecipToggleRow = _createRowWithRadioButtonList('tributeRecipientToggleRow', 'ce-tributeRecipientToggleContainers', 'tributeRecipientToggle', '', optsCsv, defOptVal, function () { _handleTributeRecipientTypeClick(); });
13037
13038                    firstTributeContactInfField.RowDomElement.insertBeforeElement(tributeRecipToggleRow);
13039
13040                    //hide of we have 1
13041                    if (optCnt <= 1)
13042                        document.getElementById('tributeRecipientToggleRow').vanish();
13043
13044                    let emailInput = getMultiSelectInputOptionElementByValue(document.getElementById('tributeRecipientToggle'), 'email');
13045                    if(emailInput != null)
13046                        _addRadioButtonLabelIcon(emailInput, 'fa fa-paper-plane');
13047
13048                    let phoneInput = getMultiSelectInputOptionElementByValue(document.getElementById('tributeRecipientToggle'), 'phone');
13049                    if(phoneInput != null)
13050                        _addRadioButtonLabelIcon(phoneInput, 'fa fa-phone');
13051
13052                    let mailInput = getMultiSelectInputOptionElementByValue(document.getElementById('tributeRecipientToggle'), 'mail');
13053                    if(mailInput != null)
13054                        _addRadioButtonLabelIcon(mailInput, 'fa fa-envelope');
13055
13056                    //let noneInput = getMultiSelectInputOptionElementByValue(document.getElementById('tributeRecipientToggle'), 'none');
13057
13058                }
13059
13060                if (thisApi.PrintCardCountField != null)
13061                    _wireUpFieldValueChangeDetectionEvent(thisApi.PrintCardCountField.Configuration, _handlePrintCardCountChanged);                
13062
13063                _togglePrintCardsVisibilityBasedOnCountSelected();
13064
13065                _toggleTributeEcardVisibilityBasedOnConfiguration();
13066
13067                _handleTributeRecipientTypeClick();
13068
13069                _tributeRecipientInforSectionAddressApi.Initialize();
13070
13071            }
13072            else
13073                _logger.Log('unable to initialize tribute buttons becuase there is no recipient contact info fields');
13074
13075        }
13076
13077    };
13078
13079    const _serializeCurrentFormFillContactInfo = function () {
13080
13081        let retVal = {};
13082
13083        retVal.ContactFirstName = thisApi.ContactFirstName;
13084        retVal.ContactMiddleName = thisApi.ContactMiddleName;
13085        retVal.ContactLastName = thisApi.ContactLastName;
13086
13087        retVal.ContactEmailAddress = thisApi.ContactEmailAddress;
13088
13089        retVal.ContactPhoneNumber = thisApi.ContactPhoneNumber;
13090
13091        retVal.ContactSingleLineAddress = _tryGetContactAddressSingleLineEntryValue();
13092        //if (document.getElementById('contactAddress_XXX_SingleLineInput') != null)
13093        //    retVal.ContactSingleLineAddress = document.getElementById('contactAddress_XXX_SingleLineInput').getValue();
13094
13095        retVal.ContactAddressStreet1 = thisApi.ContactAddressStreet1;
13096        retVal.ContactAddressStreet2 = thisApi.ContactAddressStreet2;
13097        retVal.ContactAddressCity = thisApi.ContactAddressCity;
13098        retVal.ContactAddressStateProvinceId = thisApi.ContactAddressStateProvinceId;
13099        retVal.ContactAddressPostalCode = thisApi.ContactAddressPostalCode;
13100        retVal.ContactAddressCountryId = thisApi.ContactAddressCountryId;
13101         
13102        return retVal;
13103
13104    };
13105
13106    const _getLastFormFillKey = function () { return 'ceLastFormFill_' + WebFormContext.Instance_Id; };
13107
13108    const _tryGetLastFormFillContactInfo = function () {
13109
13110        let retVal = null;
13111
13112        let strVal = tryGetLocalStorageItem(_getLastFormFillKey());
13113
13114        _logger.Log('try get last form fill: ' + strVal);
13115
13116        if (!isBlank(strVal) && strVal != '{}') {
13117
13118            try {
13119                retVal = JSON.parse(strVal);
13120            }
13121            catch {
13122                _logger.Log('failed to parse last form fill');
13123            }
13124
13125        }
13126        
13127
13128        return retVal;
13129
13130    };
13131
13132    const _saveLastFormFillContactInfo = function () {
13133            
13134        let cnt = _serializeCurrentFormFillContactInfo();
13135
13136        let strVal = JSON.stringify(cnt);
13137
13138        _logger.Log('try save last form fill: ' + strVal);
13139
13140        trySetLocalStorageItem(_getLastFormFillKey(), strVal);
13141
13142    };
13143
13144    const _tryPopulateFormFromLastFormFill = function () {
13145
13146        let cnt = _tryGetLastFormFillContactInfo();
13147        if (cnt != null) {
13148
13149            _logger.Log('populate form from last user fill');
13150
13151            //only populate if values are blank
13152            let isNameEmpty = (isBlank(thisApi.ContactFirstName) && isBlank(thisApi.ContactMiddleName) && isBlank(thisApi.ContactLastName));
13153            if (isNameEmpty) {
13154
13155                if(typeof cnt.ContactFirstName != 'undefined' && !isBlank(cnt.ContactFirstName))
13156                    _trySetFieldValue(thisApi.ContactFirstNameField, cnt.ContactFirstName);
13157
13158                if(typeof cnt.ContactMiddleName != 'undefined' && !isBlank(cnt.ContactMiddleName))
13159                    _trySetFieldValue(thisApi.ContactMiddleNameField, cnt.ContactMiddleName);
13160
13161                if(typeof cnt.ContactLastName != 'undefined' && !isBlank(cnt.ContactLastName))
13162                    _trySetFieldValue(thisApi.ContactLastNameField, cnt.ContactLastName);
13163
13164            }
13165
13166            if (isBlank(thisApi.ContactEmailAddress) && (typeof cnt.ContactEmailAddress != 'undefined' && !isBlank(cnt.ContactEmailAddress)))                 
13167                _trySetFieldValue(thisApi.ContactEmailAddressField, cnt.ContactEmailAddress);            
13168
13169            if (isBlank(thisApi.ContactPhoneNumber) && (typeof cnt.ContactPhoneNumber != 'undefined' && !isBlank(cnt.ContactPhoneNumber)))
13170                _trySetFieldValue(thisApi.ContactPhoneNumberField, cnt.ContactPhoneNumber);
13171                            
13172            let isAddressEmpty = (isBlank(thisApi.ContactAddressStreet1) && isBlank(thisApi.ContactAddressStreet2) && isBlank(thisApi.ContactAddressCity) && isBlank(thisApi.ContactAddressStateProvinceCode) && isBlank(thisApi.ContactAddressPostalCode));
13173            if (isAddressEmpty) { 
13174
13175                const cntSlaEntElem = _tryGetContactAddressSingleLineEntryDomElement();
13176                if (cntSlaEntElem != null && isBlank(cntSlaEntElem.getValue()) && (typeof cnt.ContactSingleLineAddress != 'undefined' && !isBlank(cnt.ContactSingleLineAddress))) 
13177                    cntSlaEntElem.setValue(cnt.ContactSingleLineAddress);
13178
13179                if (typeof cnt.ContactAddressStreet1 != 'undefined' && !isBlank(cnt.ContactAddressStreet1))
13180                    _trySetFieldValue(thisApi.ContactAddressStreet1Field, cnt.ContactAddressStreet1);
13181
13182                if (typeof cnt.ContactAddressStreet2 != 'undefined' && !isBlank(cnt.ContactAddressStreet2))
13183                    _trySetFieldValue(thisApi.ContactAddressStreet2Field, cnt.ContactAddressStreet2);
13184
13185                if (typeof cnt.ContactAddressCity != 'undefined' && !isBlank(cnt.ContactAddressCity))
13186                    _trySetFieldValue(thisApi.ContactAddressCityField, cnt.ContactAddressCity);                              
13187
13188                if (typeof cnt.ContactAddressStateProvinceId != 'undefined')
13189                    _trySetFieldValue(thisApi.ContactAddressStateProvinceField, cnt.ContactAddressStateProvinceId);
13190
13191                if (typeof cnt.ContactAddressPostalCode != 'undefined' && !isBlank(cnt.ContactAddressPostalCode))
13192                    _trySetFieldValue(thisApi.ContactAddressPostalCodeField, cnt.ContactAddressPostalCode);
13193
13194                if (typeof cnt.ContactAddressCountryId != 'undefined')
13195                    _trySetFieldValue(thisApi.ContactAddressCountryField, cnt.ContactAddressCountryId);
13196
13197            }
13198
13199        }
13200
13201    };
13202
13203    const _initializeVisitorAnonymousContextInfo = function () {
13204
13205        //initialize context
13206        _logger.Log('initializing web visitor data analysis engine');
13207
13208        _logger.Log('initiate geolocation analysis for web visitor IP address ' + WebFormContext.VisitorLocation.IPAddress);
13209
13210        _logger.Log('begin location analysis...');
13211        _logger.Log('country identified ' + WebFormContext.VisitorLocation.Country.Name);
13212
13213        //TODO: create currency conversion rates dictionary in context and pass to conversion function
13214        _logger.Log('perform country localization to default currency ' + WebFormContext.VisitorLocation.Country.DefaultCurrency.Name);
13215
13216        _logger.Log('city identified ' + WebFormContext.VisitorLocation.City);
13217
13218        _logger.Log('begin demographic analysis...');
13219        _logger.Log('demographic income group ' + WebFormContext.VisitorLocation.DemographicInfo.LocationMedianIncomeGroup_Id);
13220        _logger.Log('demographic median income floor ' + WebFormContext.VisitorLocation.DemographicInfo.LocationMedianIncomeFloor);
13221
13222        _logger.Log('geolocation analysis completed')
13223
13224        let initDefaultId = Currencies.USD;
13225
13226        if (WebFormContext.VisitorLocation.Country.DefaultCurrency.Id > 0)
13227            initDefaultId = WebFormContext.VisitorLocation.Country.DefaultCurrency.Id;
13228
13229        _logger.Log('try to set currency to ' + initDefaultId);
13230
13231        _trySetFieldValue(thisApi.PaymentCurrencyField, initDefaultId);
13232
13233        if (WebFormContext.VisitorLocation.LocationMedianIncomeGroup_Id > 0)
13234            _reinitializeAskAmounts(0, WebFormContext.VisitorLocation.LocationMedianIncomeGroup_Id);
13235        //else
13236            //_reinitializeAskAmounts(0); //needed for startup
13237
13238    };
13239
13240    const _initializeKnownVisitorContext = function () {        
13241
13242        let cntxCntId = getWebAppVisitorContextContactId();
13243
13244        _logger.Log('init visitor: ' + cntxCntId);
13245
13246        //only register and fire if we have an identified visitor
13247        if (cntxCntId > 0) {
13248
13249            App.Captcha.RegisterInitializedCallBack(function () {
13250
13251                _logger.Log('call get form visitor context info (from captcha initialized event)');
13252
13253                //switched to local debug request - need to test in beta with real browser.
13254                ////needed to prevent captcha errors due to timing of post after page load with no UI interactions
13255                //setTimeout(function () {
13256
13257                //try get contact (note: dependent on CAS which is injected prior to form initialization on page)
13258                App.GetUrlAsync('/api/PublicWebAppUIAjaxEndpoint/GetFormVisitorContextInfo',
13259                    function (result) {
13260
13261                        _logger.Log('get form visitor context: ' + JSON.stringify(result));
13262
13263                        //if (isBlank(thisApi.ContactFirstName) && isBlank(thisApi.ContactMiddleName) && isBlank(thisApi.ContactLastName)) {
13264                        //                        
13265                        //        _trySetFieldValue(thisApi.ContactFirstNameFieldField,result.FirstName);                        
13266                        //        _trySetFieldValue(thisApi.ContactMiddleNameFieldField,result.MiddleName);                                                
13267                        //        _trySetFieldValue(thisApi.ContactLastNameFieldField,result.LastName);
13268                        //
13269                        //}
13270
13271                        _tryPopulateFormFromLastFormFill();
13272
13273                        //ask string customization
13274                        if (result.HighestPreviousTransactionAmount > 0)
13275                            _reinitializeAskAmounts(result.HighestPreviousTransactionAmount, 1);
13276
13277                        //configure one-click 
13278                        _initializeOneClickCheckout(cntxCntId, result.PaymentProfile_Id, result.PaymentProfileDescription);
13279
13280                    },
13281                    function (error) {
13282                        _logger.Log('error: ' + error);
13283                    });
13284
13285                //}, 5000);
13286
13287            });
13288
13289        }
13290        else
13291        {
13292            _logger.Log('skip visitor context lookup (anonymous)');
13293            _tryPopulateFormFromLastFormFill();
13294        }
13295            
13296    };
13297
13298    const _getQuantityFieldForTicket = function (ticket_id) {
13299
13300        let retVal = null;
13301
13302        if (ticket_id > 0) {
13303
13304            let qtyFields = _getTicketQuantityFields();
13305            for (let x = 0; x < qtyFields.length; x++)
13306                if (qtyFields[x].DomElement.tryGetIntegerAttribute('data-ticket-id') == ticket_id) {
13307                    retVal = qtyFields[x];
13308                    break;
13309                }
13310
13311        }
13312
13313        return retVal;
13314
13315    };
13316
13317    const _getSubtotalFieldForTicket = function (ticket_id) {
13318
13319        let retVal = null;
13320
13321        if (ticket_id > 0) {
13322
13323            for (let x = 0; x < thisApi.TicketSubTotalFields.length; x++)
13324                if (thisApi.TicketSubTotalFields[x].DomElement.tryGetIntegerAttribute('data-ticket-id') == ticket_id) {
13325                    retVal = thisApi.TicketSubTotalFields[x];
13326                    break;
13327                }
13328
13329        }
13330
13331        return retVal;
13332
13333    };
13334
13335    const _handleTicketQuantityChanged = function () {
13336
13337        _updateFormAfterAmountImpactingChange(); //update cost amount
13338
13339        for (let x = 0; x < thisApi.TicketQuantityFields.length; x++)
13340            if (thisApi.TicketQuantityFields[x].DomElement != null) {
13341
13342                const ticket_id = thisApi.TicketQuantityFields[x].DomElement.tryGetIntegerAttribute('data-ticket-id');
13343
13344                const subTotaField = _getSubtotalFieldForTicket(ticket_id);
13345                if (subTotaField != null) {
13346
13347                    const subTotal = (thisApi.TicketQuantityFields[x].Value * thisApi.TicketQuantityFields[x].DomElement.tryGetDecimalAttribute('data-ticket-price'));
13348
13349                    const amtForm = _getAmountFormatted(subTotal);
13350
13351                    subTotaField.SetValue(amtForm);
13352
13353                }
13354
13355                const meOrSomeoneElseApi = _getTicketMeOrSomeoneElseApi(ticket_id);
13356                if (meOrSomeoneElseApi != null)
13357                    meOrSomeoneElseApi.HandleItemQuantityChanged();
13358
13359                const multipleTicketsOtherRegistrantsApi = _getMultipleTicketsOtherRegistrantsApi(ticket_id);
13360                if (multipleTicketsOtherRegistrantsApi != null)
13361                    multipleTicketsOtherRegistrantsApi.HandleItemQuantityChanged();
13362
13363                const addContactsApi = _getTicketAdditionalContactsApi(ticket_id);
13364                if (addContactsApi != null)
13365                    addContactsApi.HandleItemQuantityChanged();
13366
13367            }
13368
13369    };
13370
13371    const _hideTicketSessionFieldsWithNoOptions = function () {
13372
13373        for (let x = 0; x < thisApi.TicketSessionFields.length; x++)
13374            if (!thisApi.TicketSessionFields[x].HasOptions)
13375                !thisApi.TicketSessionFields[x].Hide();
13376
13377    };
13378
13379    const _ticketMeOrSomeoneElseApis = [];
13380    const _multipleTicketsOtherRegistrantApis = [];
13381    const _ticketGuestAdditionalContactApis = [];
13382
13383    const _getTicketMeOrSomeoneElseApi = function (ticket_id) {
13384
13385        let retVal = null;
13386
13387        for (let x = 0; x < _ticketMeOrSomeoneElseApis.length; x++) 
13388            if (_ticketMeOrSomeoneElseApis[x].RelatedElementReference_Id == ticket_id) {
13389                retVal = _ticketMeOrSomeoneElseApis[x];
13390                break;
13391            }        
13392
13393        return retVal;
13394
13395    };
13396
13397    const _getMultipleTicketsOtherRegistrantsApi = function (ticket_id) {
13398
13399        let retVal = null;
13400
13401        for (let x = 0; x < _multipleTicketsOtherRegistrantApis.length; x++) 
13402            if (_multipleTicketsOtherRegistrantApis[x].RelatedElementReference_Id == ticket_id) {
13403                retVal = _multipleTicketsOtherRegistrantApis[x];
13404                break;
13405            }        
13406
13407        return retVal;
13408
13409    };
13410
13411    const _getTicketAdditionalContactsApi = function (ticket_id) {
13412
13413        let retVal = null;
13414
13415        for (let x = 0; x < _ticketGuestAdditionalContactApis.length; x++) 
13416            if (_ticketGuestAdditionalContactApis[x].RelatedElementReference_Id == ticket_id) {
13417                retVal = _ticketGuestAdditionalContactApis[x];
13418                break;
13419            }        
13420
13421        return retVal;
13422
13423    };
13424
13425    const _handleReduceTicketQuantityClick = function (ticket_id) {        
13426
13427        let tikQtyField = _getQuantityFieldForTicket(ticket_id);
13428        if (tikQtyField != null) {
13429
13430            let curVal = tikQtyField.Value;
13431            if (curVal > 0) {
13432
13433                curVal--;
13434
13435                tikQtyField.SetValue(curVal);
13436
13437            }
13438
13439        }
13440
13441    };
13442
13443    const _isTicketPurchaseEnabled = function (fldApi) {
13444
13445        return (fldApi.DomElement == null || fldApi.DomElement.getAttribute('data-ticket-enabled') != 'false');
13446
13447    };
13448
13449    const _handleIncreaseTicketQuantityClick = function (ticket_id) {
13450
13451        let tikQtyField = _getQuantityFieldForTicket(ticket_id);
13452        if (tikQtyField != null && _isTicketPurchaseEnabled(tikQtyField)) {
13453
13454            let maxAllowed = _getTicketMaxAllowed(tikQtyField);
13455
13456            let curVal = tikQtyField.Value;
13457            if (maxAllowed <= 0 || curVal < maxAllowed) {
13458
13459                curVal++;
13460
13461                tikQtyField.SetValue(curVal);
13462
13463            }
13464
13465        }
13466
13467    };
13468
13469    const _initializeTicketQuantityFieldDependencies = function () {
13470
13471        //FOR NOW WE ALWAYS HIDE - maybe we show when there's an amount later, but TBD
13472        //(done unconditionally for every ticket, not just ones with an active quantity control, so a ticket rendered as a sale-window message still gets its subtotal hidden)
13473        for (let s = 0; s < thisApi.TicketSubTotalFields.length; s++)
13474            thisApi.TicketSubTotalFields[s].Hide();
13475
13476        for (let x = 0; x < thisApi.TicketQuantityFields.length; x++) {
13477
13478            const fldApi = thisApi.TicketQuantityFields[x];
13479            const fe = fldApi.DomElement;
13480            const tid = fe.tryGetIntegerAttribute('data-ticket-id');
13481
13482            //console.log('fldApi.Configuration.SystemFieldRenderType_Id: ' + fldApi.Configuration.SystemFieldRenderType_Id);
13483            //console.log('FORM_SYSTEM_FIELD_RENDER_TYPES.TextBox: ' + FORM_SYSTEM_FIELD_RENDER_TYPES.TextBox);
13484            if (fldApi.Configuration.SystemFieldRenderType_Id == FORM_SYSTEM_FIELD_RENDER_TYPES.TextBox) {
13485                 
13486                //add minus button
13487                const tikMinusButton = _generateFormFieldButton('reduceTicketQuantity_' + tid, function (btn) { _handleReduceTicketQuantityClick(btn.tryGetIntegerAttribute('data-ticket-id')); }, 'reduce number of tickets');
13488                _addFormFieldButtonIconCss(tikMinusButton, 'fa fa-minus');
13489                tikMinusButton.setAttribute('data-ticket-id', tid);
13490                fldApi.WrapperDomElement.appendChild(tikMinusButton);
13491
13492                const tikPlusButton = _generateFormFieldButton('increaseTicketQuantity_' + tid, function (btn) { _handleIncreaseTicketQuantityClick(btn.tryGetIntegerAttribute('data-ticket-id')); }, 'increase number of tickets');
13493                _addFormFieldButtonIconCss(tikPlusButton, 'fa fa-plus');
13494                tikPlusButton.setAttribute('data-ticket-id', tid);
13495                fldApi.WrapperDomElement.appendChild(tikPlusButton);
13496
13497            }
13498
13499            //Me or someone else UX
13500            const meOrSomeoneElseRow = _createFormRow('meOrSomeoneElseWidgetRow_' + tid, 'ce-meOrSomeoneElseWidgetRows');
13501
13502            fldApi.SectionDomElement.insertAfterElement(meOrSomeoneElseRow, fldApi.RowDomElement);
13503
13504            const newMeOrSomoeneElseApi = getCheckOutItemForMeOrSomeoneElseApi({ "ItemQuantityFieldDomId": fe.id, "RelatedElementContainerDomId": fldApi.RowDomElement.id, "WidgetContainerDomId": meOrSomeoneElseRow.id, "EmailAddressRequired": false, "PhoneNumberRequired": false, "ToggleLableCheckedText": 'I am an attendee', "ToggleLableNotCheckedText": '', "RelatedElementReference_Id": tid, "AutoInitialize": false });
13505            newMeOrSomoeneElseApi.Initialize();
13506            _ticketMeOrSomeoneElseApis.push(newMeOrSomoeneElseApi);            
13507             
13508            //Additional registrants (multi-ticket)
13509            const multiTicketAdditionalAttendeesRowRow = _createFormRow('additionalAttendeesWidgetRow_' + tid, 'ce-additionalContactsWidgetRows');
13510
13511            fldApi.SectionDomElement.insertAfterElement(multiTicketAdditionalAttendeesRowRow, meOrSomeoneElseRow);
13512
13513            const multipleTicketsOtherRegistrantsApi = getAddAdditionalContactsApi({ "ItemQuantityFieldDomId": fe.id, "RelatedElementContainerDomId": fldApi.RowDomElement.id, "WidgetContainerDomId": multiTicketAdditionalAttendeesRowRow.id, "MaximumContactsPerItem": 1, "GetMaximumContactsFunction": function (qty) { return (qty - 1); }, "EmailAddressRequired": false, "PhoneNumberRequired": false, "TitleHtml": '', "AddContactButtonText": 'Add Attendee', "AddFirstContactRow": false, "RelatedElementReference_Id": tid, "AutoInitialize": false });
13514            multipleTicketsOtherRegistrantsApi.Initialize();
13515            _multipleTicketsOtherRegistrantApis.push(multipleTicketsOtherRegistrantsApi);  
13516             
13517            //Add Guests
13518            const newAdditionalContactsRow = _createFormRow('additionalContactsWidgetRow_' + tid, 'ce-additionalContactsWidgetRows');
13519
13520            fldApi.SectionDomElement.insertAfterElement(newAdditionalContactsRow, multiTicketAdditionalAttendeesRowRow);
13521             
13522            const maxGuests = _getGuestsMaxAllowed(fldApi);
13523
13524            //tickets with maxGuest 0 mean no guests allowed
13525            if (maxGuests > 0) {
13526                let newAdditionalContactsApi = getAddAdditionalContactsApi({ "ItemQuantityFieldDomId": fe.id, "RelatedElementContainerDomId": fldApi.RowDomElement.id, "WidgetContainerDomId": newAdditionalContactsRow.id, "MaximumContactsPerItem": maxGuests, "MaximumContacts": 0, "EmailAddressRequired": false, "PhoneNumberRequired": false, "TitleHtml": '', "AddContactButtonText": 'Add Guest', "AddFirstContactRow": false, "RelatedElementReference_Id": tid, "AutoInitialize": false });
13527                newAdditionalContactsApi.Initialize();
13528                _ticketGuestAdditionalContactApis.push(newAdditionalContactsApi);
13529            }
13530
13531        }
13532
13533    };    
13534
13535    const _getTicketMaxAllowed = function (fldApi) {
13536
13537        let retVal = fldApi.DomElement.tryGetIntegerAttribute('data-max-allowed');
13538
13539        return retVal;
13540
13541    };
13542
13543    const _getGuestsMaxAllowed = function (fldApi) {
13544
13545        let retVal = fldApi.DomElement.tryGetIntegerAttribute('data-max-guests-allowed');
13546
13547        return retVal;
13548
13549    }
13550
13551    const _getFormattedTicketPrice = function (fldApi) {
13552
13553        let sym = _getPaymentCurrencySymbol();
13554
13555        let tikPrice = fldApi.DomElement.tryGetDecimalAttribute('data-ticket-price');
13556
13557        return formatCurrency(tikPrice, true, sym);
13558
13559    };
13560
13561    const _initializeEventTicketsSection = function () {
13562
13563        const tikQntFlds = _getTicketQuantityFields();
13564        if (tikQntFlds.length > 0) {
13565
13566            _logger.Log('begin initialize event tickets section');
13567
13568            for (let x = 0;x < tikQntFlds.length;x++) { 
13569
13570                const fldApi = tikQntFlds[x];
13571
13572                _wireUpFieldValueChangeDetectionEvent(fldApi.Configuration, _handleTicketQuantityChanged);
13573
13574                const tikPriceFormatted = _getFormattedTicketPrice(fldApi);
13575
13576                //update label to include price
13577                if (fldApi.LabelDomElement != null)
13578                    fldApi.LabelDomElement.innerHTML += ' (' + tikPriceFormatted + ')';   
13579
13580                _addCoverProcessingCostBlock(fldApi.SectionDomElement);
13581
13582            }
13583
13584            _initializeTicketQuantityFieldDependencies();
13585
13586            _hideTicketSessionFieldsWithNoOptions();
13587
13588        }
13589
13590    };
13591      
13592    const _handleProductConfigChanged = function (product_id) {
13593
13594        let productApi = null;
13595        for (let x = 0;x < WebFormContext.Form.ProductWidgetApis.length;x++)
13596            if (WebFormContext.Form.ProductWidgetApis[x].Product_Id == product_id){
13597                productApi = WebFormContext.Form.ProductWidgetApis[x];
13598                break;
13599            }
13600             
13601        if (productApi != null) {
13602             
13603            const priceDisplayContainer = productApi.ContainerDomElement.querySelector('.ce-productPrices');
13604            if (priceDisplayContainer != null) {
13605
13606                let sym = _getPaymentCurrencySymbol(); 
13607
13608                priceDisplayContainer.innerHTML = formatCurrency(productApi.SelectionDetails.TotalPrice, true, sym);
13609                 
13610                priceDisplayContainer.vanish();
13611                if (productApi.SelectionDetails.Quantity > 0) priceDisplayContainer.show();
13612
13613            }
13614             
13615        }
13616        else
13617            _logger.Log('handle product config changed called without product_id (bug)');
13618
13619        _updateFormAfterAmountImpactingChange(); 
13620          
13621    };
13622    
13623    const _initializeProducts = function(){
13624
13625        if (typeof WebFormContext.Form.ProductWidgetApis == 'undefined' || WebFormContext.Form.ProductWidgetApis.length <= 0)
13626            return;
13627
13628        _logger.Log('begin initialize product widgets');
13629         
13630        for (let x = 0;x < WebFormContext.Form.ProductWidgetApis.length;x++) {
13631
13632            const productApi = WebFormContext.Form.ProductWidgetApis[x];
13633            
13634            productApi.Initialize(); //call initialize
13635
13636            const prodWidgetRowDomElem = productApi.ContainerDomElement;
13637
13638            const product_id = productApi.Product_Id;
13639            
13640            if (productApi.QuantityDomElement != null){
13641
13642                const qtyLblElem = productApi.QuantityDomElement.getAssociatedFieldLabel();
13643                if(qtyLblElem != null)
13644                    _initializeDynamicLabelField(productApi.QuantityDomElement, qtyLblElem);
13645                     
13646                //wire up plus/minus box
13647                getAmountTextBoxPlusMinusButtonsApi({"TextBoxDomId": productApi.QuantityDomElement.id, "MinimumValue": 0, "MaximumValue": 0, "AmountChangedCallBack": function () { _handleProductConfigChanged(product_id); } }).Initialize();
13648
13649            }
13650
13651            if (productApi.SetPriceDomElement != null) {
13652
13653                const setPriceLblElem = productApi.SetPriceDomElement.getAssociatedFieldLabel();
13654                if (setPriceLblElem != null)
13655                    _initializeDynamicLabelField(productApi.SetPriceDomElement, setPriceLblElem);
13656
13657                //wire up plus/minus box
13658                getAmountTextBoxPlusMinusButtonsApi({"TextBoxDomId": productApi.SetPriceDomElement.id, "MinimumValue": 0, "MaximumValue": 0, "AmountChangedCallBack": function () { _handleProductConfigChanged(product_id); }}).Initialize();  
13659
13660            }
13661
13662            if (productApi.AttributeDropDownDomElements != null)
13663                for (let x = 0;x < productApi.AttributeDropDownDomElements.length;x++) {
13664
13665                    const atrElem = productApi.AttributeDropDownDomElements[x];
13666
13667                    const atrLblElem = atrElem.getAssociatedFieldLabel();
13668                    if (atrLblElem != null)
13669                        _initializeDynamicLabelField(atrElem, atrLblElem);
13670
13671                    atrElem.addEventListener('change', function () {_handleProductConfigChanged(product_id); });
13672
13673                }
13674
13675            if(productApi.CustomFieldDomElements != null)
13676                for (let x = 0;x < productApi.CustomFieldDomElements.length;x++) {
13677
13678                    const cfElem = productApi.SetPriceDomElement[x];
13679
13680                    const cfLblElem = cfElem.getAssociatedFieldLabel();
13681                    if (cfLblElem != null)
13682                        _initializeDynamicLabelField(cfElem, cfLblElem);
13683
13684                    cfElem.addEventListener('change', function () {_handleProductConfigChanged(product_id);});
13685
13686                }
13687
13688            const priceDisplayContainer = productApi.ContainerDomElement.querySelector('.ce-productPrices');
13689            if(priceDisplayContainer != null) priceDisplayContainer.vanish();
13690
13691        }
13692         
13693    };
13694
13695    const _addMultiSelectFieldElementOption = function (elem, value, text, selected, attributes) {
13696
13697        let domId = elem.id + '_option_' + (elem.children.length + 1);
13698        let domName = elem.id + '_options';
13699
13700        addMultiSelectInputFieldOption(elem, value, text, selected, 'ce-formFieldOptionItems', domId, domName, attributes);
13701
13702    };
13703
13704    const _hideGooglePayOptionIfSdkNotLoaded = function () {
13705
13706        if (thisApi.PaymentMethodField != null && thisApi.PaymentMethodField.DomElement != null && !thisApi.GooglePayApi.IsGooglePayApiAvailable()) {
13707
13708            let options = thisApi.PaymentMethodField.DomElement.querySelectorAll('.ce-formFieldOptionItems');
13709            for (let x = 0; x < options.length; x++)
13710                if (options[x].tryGetIntegerAttribute('data-wallet-type-id') == WALLET_TYPES.GOOG
13710LE_PAY) {
13711                    options[x].vanish();
13712                    break;
13713                }
13714
13715        }
13716
13717    };
13718
13719    const _tryShowPaymentMethodBillingInfoFields = function () {
13720
13721        for (let x = 0; x < _allFieldApis.length; x++)
13722            if (_allFieldApis[x].IsPaymentMethodBillingInformation)
13723                _allFieldApis[x].Show();
13724
13725    };
13726
13727    const _tryHidePaymentMethodBillingInfoFields = function () {
13728
13729        for (let x = 0; x < _allFieldApis.length; x++)
13730            if (_allFieldApis[x].IsPaymentMethodBillingInformation)
13731                _allFieldApis[x].Hide();
13732
13733    };
13734
13735    const _handleToggleBillingInfoClicked = function () {
13736
13737        if (document.getElementById('togglePaymentMethodBillingInfoCheckBox') != null) {
13738
13739            let tcb = document.getElementById('togglePaymentMethodBillingInfoCheckBox').getValue();
13740            if (tcb) {
13741
13742                //only show for credit card
13743                if (thisApi.PaymentMethodField.Value == PAYMENT_METHODS.CREDIT_CARD)
13744                    _tryShowPaymentMethodBillingInfoFields();
13745
13746            }
13747            else
13748                _tryHidePaymentMethodBillingInfoFields();
13749
13750        }
13751
13752    };
13753
13754    const _tryShowPaymentMethodField = function() {
13755
13756        if (thisApi.PaymentMethodField != null && thisApi.PaymentMethodField.OptionCount > 1)
13757            thisApi.PaymentMethodField.Show();
13758
13759    };
13760
13761    const _tryHidePaymentMethodField = function() {
13762
13763        if (thisApi.PaymentMethodField != null)
13764            thisApi.PaymentMethodField.Hide();
13765
13766    };
13767
13768    const _addRadioButtonLabelIcon = function (inputElem, iconCssClasses) {
13769
13770        if (inputElem != null && !isBlank(iconCssClasses)) {
13771
13772            let lbl = inputElem.getAssociatedLabel();
13773            if (lbl != null) {
13774                
13775                lbl.addClassName('ce-radioButtonIconLabels');
13776
13777                let iconElem = document.createElement('span');
13778                iconElem.addClassNames(iconCssClasses);
13779
13780                lbl.insertFirstElement(iconElem);
13781
13782            }
13783
13784        }
13785
13786    };
13787     
13788    const _addFieldHelpIconWidget = function (field,html) {
13789         
13790        if (field != null && field.LabelDomElement != null) {
13791
13792            field.LabelDomElement.addClassName('ce-formFieldHelpIconLabels');
13793
13794            let newWidget = document.createElement('div');
13795            newWidget.addClassName('ce-formFieldInfoIconWidgets');
13796
13797            let icon = document.createElement('span');
13798            icon.addClassNames('fa fa-question-circle');
13799
13800            icon.addEventListener('mouseenter', function (event) { event.target.parentElement.querySelector('.ce-formFieldInfoIconOverlays').show(); });
13801            icon.addEventListener('mouseleave', function (event) { event.target.parentElement.querySelector('.ce-formFieldInfoIconOverlays').vanish(); });
13802
13803            newWidget.appendChild(icon);
13804
13805            let htmlOverlay = document.createElement('div');
13806            htmlOverlay.addClassName('ce-formFieldInfoIconOverlays');
13807
13808            htmlOverlay.addHtml(html);
13809
13810            htmlOverlay.vanish();
13811
13812            newWidget.appendChild(htmlOverlay);
13813            
13814            field.LabelDomElement.appendChild(newWidget);
13815
13816        }
13817         
13818    };
13819
13820    const _addBankAccountHelpIcon = function (field) {
13821        _addFieldHelpIconWidget(field,'<img src="/_sysimages/achhelper.png" class="ce-checkFieldHelperImages" />');
13822    };
13823
13824    //moves the PayPal button container into the Payment Method Section BEFORE PayPalApi.Initialize() renders
13825    //the buttons into it - the PayPal SDK (zoid) tears down/re-renders its buttons if their container is
13826    //reparented in the DOM after render(), so this must run first, not from inside _initializePaymentMethodSection
13827    const _movePayPalButtonIntoPaymentMethodSection = function () {
13828
13829        if (thisApi.PaymentMethodField != null && thisApi.PaymentMethodField.DomElement != null) {
13830
13831            let paymentMethodSecElem = thisApi.PaymentMethodField.SectionDomElement;
13832
13833            let ppElem = getMultiSelectInputOptionElementByValue(thisApi.PaymentMethodField.DomElement, PAYMENT_METHODS.PAYPAL);
13834            if (ppElem != null) {
13835
13836                if (!thisApi.PayPalApi.PayPalEnabled || !thisApi.PayPalApi.IsPayPalApiAvailable())
13837                    ppElem.firstParentOrSelfWithClass('ce-formFieldOptionItems').vanish();
13838                else {
13839                    _addRadioButtonLabelIcon(ppElem, 'fa fa-brands fa-paypal'); //todo: maybe make configurable or move to template - TBD
13840
13841                    //move PayPal button into Payment Method Section
13842                    paymentMethodSecElem.appendChild(thisApi.PayPalApi.GetButtonsContainerElement());
13843                }
13844
13845            }
13846
13847        }
13848
13849    };
13850
13851    const _initializePaymentMethodSection = function () {
13852
13853        if (thisApi.PaymentMethodField != null) {
13854            //get payment method section
13855            let paymentMethodSecElem = thisApi.PaymentMethodField.SectionDomElement;
13856
13857            //hide payment method field if there is ony one option
13858            if (thisApi.PaymentMethodField.OptionCount <= 1)
13859                _tryHidePaymentMethodField();
13860
13861            //native field event bindings 
13862            _wireUpFieldValueChangeDetectionEvent(thisApi.PaymentMethodField.Configuration, _handlePaymentMethodChanged);
13863
13864            //wire up credit card UX
13865
13866            //wire up expiration date UX 
13867            if (thisApi.CreditCardExpirationDateField != null && thisApi.CreditCardExpirationDateField.DomElement != null)
13868                thisApi.CreditCardExpirationDateField.DomElement.addEventListener('keyup', function (e) { console.log('cc exp key up'); handleMMYYCreditCardExpirationInputChange(thisApi.CreditCardExpirationDateField.DomElement, e); });
13869
13870            //wire up bank account UX
13871            if (thisApi.BankAccountNumberField != null)
13872                _addBankAccountHelpIcon(thisApi.BankAccountNumberField);
13873
13874            //wire up routing number UX
13875            if (thisApi.BankRoutingNumberField != null)
13876                _addBankAccountHelpIcon(thisApi.BankRoutingNumberField);
13877
13878            let fistBillingInfoField = _getFirstPaymentMethodBillingInfoField();
13879            if (fistBillingInfoField != null) {
13880
13881                let cb = _createRowWithSingleCheckBoxOption('togglePaymentMethodBillingInfoContainer', '', 'togglePaymentMethodBillingInfoCheckBox', true, 'Billing info is different', false, function () { _handleToggleBillingInfoClicked(); });
13882
13883                fistBillingInfoField.RowDomElement.insertBeforeElement(cb);
13884
13885                _handleToggleBillingInfoClicked();
13886
13887            }
13888
13889            if (thisApi.PaymentMethodField.DomElement != null) {
13890
13891                let ccElem = getMultiSelectInputOptionElementByValue(thisApi.PaymentMethodField.DomElement, PAYMENT_METHODS.CREDIT_CARD);
13892                if (ccElem != null) 
13893                    _addRadioButtonLabelIcon(ccElem, 'fa fa-credit-card'); //todo: maybe make configurable or move to template - TBD
13894
13895                let bnkElem = getMultiSelectInputOptionElementByValue(thisApi.PaymentMethodField.DomElement, PAYMENT_METHODS.ACH);
13896                if (bnkElem != null) 
13897                    _addRadioButtonLabelIcon(bnkElem,'fa fa-bank'); //todo: maybe make configurable or move to template - TBD        
13898
13899                let scElem = getMultiSelectInputOptionElementByValue(thisApi.PaymentMethodField.DomElement, PAYMENT_METHODS.SOFT_CREDIT);
13900                if (scElem != null) {                      
13901
13902                    if (!thisApi.DafPayPaymentMethodAvailable || !thisApi.DafPayApi.DafPayEnabled)
13903                        scElem.firstParentOrSelfWithClass('ce-formFieldOptionItems').vanish();
13904                    else {
13905
13906                        _addRadioButtonLabelIcon(scElem,'fa fa-hand-holding-heart'); //todo: maybe make configurable or move to template - TBD      
13907
13908                        //backwards compatibiliyt fix for lamo naming conventions
13909                        let scLbl = getLabelForField(scElem);
13910                        if (scLbl != null && scLbl.innerText.toLowerCase().trim() == 'soft credit')
13911                            scLbl.innerText = 'Donor Advised Fund';
13912
13913                        //move DAFPay button into Payment Method Section
13914                        paymentMethodSecElem.appendChild(thisApi.DafPayApi.GetButtonContainerElement());
13915
13916                    }
13917
13918                }
13919
13920
13921                if (thisApi.GooglePayApi.GooglePayEnabled) {
13922
13923                    let gpAttrs = [];
13924                    gpAttrs.push({ "key": 'data-wallet-type-id', "value": WALLET_TYPES.GOOG
13924LE_PAY });
13925
13926                    _addMultiSelectFieldElementOption(thisApi.PaymentMethodField.DomElement, PAYMENT_METHODS.CREDIT_CARD, 'GooglePay', false, gpAttrs);
13927
13928                    let newGpElem = document.querySelector('span.ce-formFieldOptionItems[data-wallet-type-id="' + WALLET_TYPES.GOOGLE_PAY + '"] input');
13929                    if (newGpElem != null) {
13930                        _addRadioButtonLabelIcon(newGpElem, 'fa fa-brands fa-google'); //todo: maybe make configurable or move to template - TBD  
13931
13932                        //move GooglePay button into Payment Method Section
13933                        paymentMethodSecElem.appendChild(thisApi.GooglePayApi.GetButtonContainerElement());
13934                    }
13935
13936                    _hideGooglePayOptionIfSdkNotLoaded();
13937
13938                }
13939
13940                if (thisApi.ApplePayApi.ApplePayEnabled && thisApi.ApplePayApi.IsApplePayApiAvailable()) {
13941
13942                    let apAttrs = [];
13943                    apAttrs.push({ "key": 'data-wallet-type-id', "value": WALLET_TYPES.APPLE_PAY });
13944
13945                    _addMultiSelectFieldElementOption(thisApi.PaymentMethodField.DomElement, PAYMENT_METHODS.CREDIT_CARD, 'ApplePay', false, apAttrs);
13946
13947                    let newApElem = document.querySelector('span.ce-formFieldOptionItems[data-wallet-type-id="' + WALLET_TYPES.APPLE_PAY + '"] input');
13948                    if (newApElem != null) {
13949                        _addRadioButtonLabelIcon(newApElem, 'fa fa-brands fa-apple'); //todo: maybe make configurable or move to template - TBD  
13950
13951                        //move ApplePay button into Payment Method Section
13952                        paymentMethodSecElem.appendChild(thisApi.ApplePayApi.GetButtonContainerElement());
13953                    }
13954
13955                }
13956
13957            }
13958
13959        }
13960        else 
13961            thisApi.ShowSubmitButton(); //_hideAllPaymentFields();        
13962
13963    };
13964
13965    const _recalculateMembershipDisplayRate = function () {
13966
13967        _logger.Log('recalculate memership rate display');
13968
13969        let memTpId = thisApi.MembershipTypeId;
13970
13971        _logger.Log('membership type ' + memTpId);
13972         
13973        let mtRate = _getSelectedMembershipRate();
13974        let durationDays = _getSelectedMembershipDurationDays();
13975        let allowSubMembers = getMultiSelectInputSelectedOptionBoolAttributeValue(thisApi.MembershipTypeField.DomElement,'data-membership-allow-sub-members');
13976        let maxSubMembers = getMultiSelectInputSelectedOptionIntegerAttributeValue(thisApi.MembershipTypeField.DomElement,'data-membership-maximum-sub-members');           
13977
13978        if (allowSubMembers)
13979            _membershipAdditionalContactApi.Show();
13980        else
13981            _membershipAdditionalContactApi.Hide();
13982
13983        _logger.Log('set maximum sub members to ' + maxSubMembers);
13984        _membershipAdditionalContactApi.SetMaximumContactsPerItem(maxSubMembers);
13985
13986        if (durationDays <= 0)
13987            durationDays = 365;
13988        
13989        _logger.Log('membership type rate ' + mtRate + ' duration ' + durationDays);
13990
13991        //TODO: need to evaluate whether this should drive membership frequency or if we need a membership specific UI (what do we do if there's an additional monthly gift?)
13992        let memPayFreq_id = _getMembershipPaymentPlanFrequencyId();
13993
13994        //calculate rate based on frequency (payment plan)
13995        let displayRate = mtRate;
13996
13997        if (displayRate > 0) {
13998
13999            switch (memPayFreq_id) {
14000                case PaymentFrequencies.Annually:
14001                    displayRate = mtRate;
14002                    break;
14003                case PaymentFrequencies.BiAnnually:
14004                    displayRate = (mtRate / 2); //Math.round
14005                    break;
14006                case PaymentFrequencies.Quarterly:
14007                    displayRate = (mtRate / 4); //Math.round
14008                    break;
14009                case PaymentFrequencies.Monthly:
14010                    displayRate = (mtRate / 12); //Math.round
14011                    break;
14012                case PaymentFrequencies.Weekly:
14013                    displayRate = (mtRate / 52); //Math.round
14014                    break;
14015                case PaymentFrequencies.Daily:
14016                    displayRate = (mtRate / 365); //Math.round
14017                    break;
14018            }
14019
14020        }
14021
14022        let memDisp = _getSelectedMembershipRateDisplayElem();        
14023        if (memDisp != null)
14024            memDisp.innerHTML = '<span class="ce-formFieldCurrencyPrefixes">$</span>' + formatCurrency(displayRate, true);        
14025
14026    };
14027
14028    const _handleMembershipTypeChanged = function () {
14029
14030        let memTpId = thisApi.MembershipTypeId; 
14031
14032        _logger.Log('membership type changed to ' + memTpId);
14033         
14034        _recalculateMembershipDisplayRate();    
14035
14036        _handlePaymentAmountChanged();
14037
14038    };
14039
14040    const _handleMembershipTypeSelectorChanged = function () { _logger.Log('membership type selector changed handler fired'); _handleMembershipTypeChanged(); };
14041
14042    const _handleMembershipPaymentFrequencyChanged = function () { _logger.Log('membership frequency changed handler fired'); _recalculateMembershipDisplayRate(); };
14043
14044    let _membershipAdditionalContactApi = null;
14045
14046    const _getSelectedMembershipRateDisplayElem = function () { return document.getElementById('membershipDisplayRate'); }
14047
14048    const _getSelectedMembershipRate = function () {
14049
14050        let retVal = 0.00;
14051
14052        if(thisApi.MembershipTypeField != null)
14053            retVal = getMultiSelectInputSelectedOptionDecimalAttributeValue(thisApi.MembershipTypeField.DomElement, 'data-membership-rate');
14054
14055        return retVal;
14056
14057    };
14058
14059    //TODO: need to wrie up data-membership-tax-deductible-amount
14060    const _getSelectedMembershipTaxDeductibleAmount = function () {
14061
14062        let retVal = 0.00;
14063
14064        if(thisApi.MembershipTypeField != null)
14065            retVal = getMultiSelectInputSelectedOptionDecimalAttributeValue(thisApi.MembershipTypeField.DomElement, 'data-membership-tax-deductible-amount');
14066
14067        return retVal;
14068
14069    };
14070
14071    const _getSelectedMembershipDurationDays = function () {
14072
14073        let retVal = 0;
14074
14075        if(thisApi.MembershipTypeField != null)
14076            retVal = getMultiSelectInputSelectedOptionIntegerAttributeValue(thisApi.MembershipTypeField.DomElement, 'data-membership-duration-days');
14077
14078        return retVal;
14079
14080    };
14081
14082    //todo: evaluate new UI
14083    const _getMembershipPaymentPlanFrequencyId = function () { return PaymentFrequencies.Annually; };
14084
14085    const _initializeMembershipSection = function () {
14086
14087        //NOTE: tribute dependent on amount section - we're putting the checkbox there for now.        
14088        if (thisApi.MembershipTypeField != null) {
14089
14090            _logger.Log('begin initialize membership section (membership type field present)');
14091
14092            //add display amount field after membership             
14093            _addRowWithSingleDisplayElement(thisApi.MembershipTypeField.SectionDomElement, 'membershipDisplayRateRow', 'ce-membershipDisplayRateRows', 'membershipDisplayRate', 'Rate', '');
14094
14095            _addCoverProcessingCostBlock(thisApi.MembershipTypeField.SectionDomElement);
14096
14097            //TODO: we need to make sure visible frequency is displayed after the current field
14098            ////if (thisApi.RecurringPaymentFrequencyField != null && thisApi.RecurringPaymentFrequencyField.Configuration.Type.Id != FORM_FIELD_TYPES.HIDDEN_FIELD) { 
14099
14100            _addRowWithSingleCheckBoxOption(thisApi.MembershipTypeField.SectionDomElement, 'autoRenewMembershipCheckBoxRow', 'ce-autoRenewMembershipCheckBoxes', 'autoRenewMembershipCheckBox', true, 'Automatically renew my membership', true); //, function () { _handleAutoRenewmembershipClick(); }
14101
14102            let newSubMembershipRow = _createFormRow('membershipAdditionalContactsWidgetRow', 'ce-additionalContactsWidgetRows');
14103
14104            thisApi.MembershipTypeField.SectionDomElement.appendChild(newSubMembershipRow);
14105
14106            _membershipAdditionalContactApi = getAddAdditionalContactsApi({ "RelatedElementContainerDomId": thisApi.MembershipTypeField.RowDomElement.id, "WidgetContainerDomId": newSubMembershipRow.id, "MaximumContacts": 0, "EmailAddressRequired": false, "PhoneNumberRequired": false, "TitleHtml": '', "AddContactButtonText": 'Add Member', "AddFirstContactRow": false, "RelatedElementReference_Id": 0, "AutoInitialize": false });
14107            _membershipAdditionalContactApi.Initialize();            
14108            _membershipAdditionalContactApi.Hide(); //default
14109
14110            _wireUpFieldValueChangeDetectionEvent(thisApi.MembershipTypeField.Configuration, _handleMembershipTypeSelectorChanged);
14111
14112            _handleMembershipTypeSelectorChanged();
14113
14114            //TODO: need to evaluate whether this should drive membership frequency or if we need a membership specific UI (what do we do if there's an additional monthly gift?)            
14115            //thisApi.RegisterPaymentRecurringFrequencyChangedCallBack(_handleMembershipPaymentFrequencyChanged);                         
14116
14117        }
14118
14119    };    
14120
14121    const _tryInitializeDatePickerField = function (domElem) {
14122
14123        if (domElem != null && typeof flatpickr !== 'undefined') {
14124
14125            flatpickr(domElem,
14126                {
14127                    enableTime: false,
14128                    dateFormat: "n/j/Y"
14129                });
14130
14131        }
14132        else
14133            _logger.Log('date field cannot initialize (flatpikr script not installed)');
14134
14135    };
14136
14137    const _tryInitializeDateTimePickerField = function (domElem) {
14138
14139        if (domElem != null && typeof flatpickr !== 'undefined') {
14140
14141            flatpickr(domElem,
14142                {
14143                    enableTime: true,
14144                    dateFormat: "n/j/Y h:i K",
14145                    time_24hr: false
14146                });
14147
14148        }
14149        else
14150            _logger.Log('date field cannot initialize (flatpikr script not installed)');
14151
14152    };
14153
14154    const _initializePledgeSection = function () {
14155
14156        if (WebFormContext.Form.Type.Id == FORM_TYPES.Pledge) {
14157
14158            if (thisApi.RecurringPaymentStartDateField != null) 
14159                _tryInitializeDatePickerField(thisApi.RecurringPaymentStartDateField.DomElement);            
14160
14161        }
14162
14163    };
14164
14165    const _initializeVolunteerPostTimeEntryWidget = function () {
14166
14167        const timeEntryInputs = document.querySelectorAll('.ce-volunteerPostTimeEntryStartDates,.ce-volunteerPostTimeEntryEndDates');
14168        for (let x = 0; x < timeEntryInputs.length; x++)
14169            _tryInitializeDateTimePickerField(timeEntryInputs[x]);
14170
14171    };
14172     
14173    const _initializeFileUploadFields = function () {
14174
14175        let fufs = _getFileUploadFieldApis();
14176
14177        if (fufs.length > 0) {
14178
14179            _logger.Log('initialize ' + fufs.length + ' file upload fields');
14180
14181            for (let x = 0; x < fufs.length; x++) {
14182
14183                fufs[x].DomElement.addEventListener('change', function () {
14184                     
14185                    _logger.Log('file changed for field ' + this.id);
14186
14187                    let fldApi = _getFieldByDomId(this.id);
14188                    if (fldApi != null) {
14189
14190                        //let ext = fldApi.DomElement.files[0].name.getFileExtension();
14191
14192                        fldApi.SetFileNameValue('');
14193                        fldApi.SetBase64Value('');
14194
14195                        readFileInput(fldApi.DomElement.files[0],
14196                            function (fileData) {
14197
14198                                _logger.Log('file ' + fldApi.DomElement.files[0].name + ' set with data (' + fileData.Base64Data.length + ')');
14199
14200                                fldApi.SetFileNameValue(fldApi.DomElement.files[0].name);
14201                                fldApi.SetBase64Value(fileData.Base64Data);
14202                                 
14203                            });
14204
14205                    }
14206                    else
14207                        _logger.Log('unable to find api for field (file content save aborted)');
14208
14209                });
14210
14211            }
14212
14213        }
14214
14215    };
14216
14217    //called by update form for payment impacting change, so no need to wire up
14218    const _refreshFreeGiftSectionBasedOnAmountChange = function () {
14219
14220        if (thisApi.FreeGiftSelectorField != null) {             
14221
14222            let paymentAmount = thisApi.GetTransactionAmount();
14223
14224            thisApi.RecurringPaymentFrequencyId > 0
14225
14226            let coverFeesAmt = _calculateFees();
14227            let recurringAmount = thisApi.RecurringPaymentAmount + coverFeesAmt;
14228
14229            if (thisApi.RecurringPaymentFrequencyId <= 0)
14230                recurringAmount = 0;
14231
14232            _logger.Log('refreshing free gift section after amount change (one-time: ' + paymentAmount + ', recurring: ' + recurringAmount + ')');
14233
14234            let availableFreeGiftCount = 0;
14235
14236            //loop through to get and check 
14237            let freeGiftElems = document.querySelectorAll('.ce-freeGifts .ce-formFieldOptionItems');
14238            for (let x = 0; x < freeGiftElems.length; x++) {
14239
14240                _logger.Log('data-minimum-amount: ' + freeGiftElems[x].tryGetDecimalAttribute('data-minimum-amount'));
14241                _logger.Log('data-minimum-recurring-amount: ' + freeGiftElems[x].tryGetDecimalAttribute('data-minimum-recurring-amount'));
14242
14243                if (paymentAmount >= freeGiftElems[x].tryGetDecimalAttribute('data-minimum-amount') && recurringAmount >= freeGiftElems[x].tryGetDecimalAttribute('data-minimum-recurring-amount')) {
14244
14245                    freeGiftElems[x].show();
14246
14247                    availableFreeGiftCount++;
14248
14249                }
14250                else { 
14251                    freeGiftElems[x].vanish();    
14252                    freeGiftElems[x].querySelector('input[type="radio"]').checked = false;
14253                }
14254
14255            }
14256
14257            if (availableFreeGiftCount > 0)
14258                thisApi.FreeGiftSelectorField.Show();
14259            else            
14260                thisApi.FreeGiftSelectorField.Hide(); 
14261
14262            //hide section if it is a free gift section type
14263            if (thisApi.FreeGiftSelectorField.Section.Configuration.SystemSectionType == FORM_SECTION_TYPES.Free_Gift) {
14264
14265                if (availableFreeGiftCount > 0)
14266                    thisApi.FreeGiftSelectorField.Section.Show();
14267                else
14268                    thisApi.FreeGiftSelectorField.Section.Hide();
14269
14270            }
14271
14272            //console.log('thisApi.FreeGiftSelectorProductId: ' + thisApi.FreeGiftSelectorProductId);   
14273
14274        }
14275
14276    };
14277     
14278    const _initializeDoubleTheDonation = function () {
14279
14280        if (!isBlank(WebFormContext.Form.DoubleTheDonationPublicKey) && document.getElementById('dd-container') == null) { 
14281
14282            let ddDiv = document.createElement('div');
14283            ddDiv.id = 'dd-container';
14284            ddDiv.vanish();
14285
14286            if (thisApi.HeaderContainerElement != null && thisApi.HeaderContainerElement.parentElement != null)
14287                thisApi.HeaderContainerElement.parentElement.insertAfterElement(ddDiv, thisApi.HeaderContainerElement);
14288            else
14289                _logger.Log('unable to add double the donation widget (no header container)');
14290 
14291        }
14292        else
14293            _logger.Log('skipping double the donation initialization (disabled or already initailized)');
14294         
14295    };
14296
14297    let _urlAmount = null;
14298    let _urlCurrencyId = null;
14299    let _urlFrequencyId = null;    
14300    let _urlPaymentMethodId = null;
14301        
14302    let _isUrlAmountProvided = false;
14303    let _isUrlPaymentMethodProvided = false; 
14304
14305    const _setFormDefaultValuesFromUrl = function () {
14306
14307        for (let x = 0; x < _allFieldApis.length; x++) { 
14308
14309            let fld = _allFieldApis[x];
14310            let fldConfig = fld.Configuration;
14311             
14312            let domIdUrlParam = getURLParameterByName(fldConfig.DomId);            
14313            if (!isBlank(domIdUrlParam)) {
14314
14315                _logger.Log('set field ' + fldConfig.DomId + ' default value based on dom id url parameter');
14316                fld.SetValue(domIdUrlParam);
14317
14318                if (_isGenericAmountInputField(fldConfig))
14319                    _urlAmount = domIdUrlParam.toDecimalZeroIfInvalid();
14320
14321                //todo: add non-id mapping attempt
14322                if (_isPaymentCurrencyField(fldConfig))
14323                    _urlCurrencyId = domIdUrlParam.toIntZeroIfInvalid();
14324
14325                //todo: add non-id mapping attempt
14326                if (_isRecurringFrequencyField(fldConfig))
14327                    _urlFrequencyId = domIdUrlParam.toIntZeroIfInvalid();
14328
14329                //todo: add non-id mapping attempt
14330                if (_isPaymentMethodField(fldConfig))
14331                    _urlPaymentMethodId = domIdUrlParam.toIntZeroIfInvalid();
14332
14333            }
14334
14335            let nameUrlParam = getURLParameterByName(fldConfig.Name);
14336            if (!isBlank(nameUrlParam)) {
14337
14338                _logger.Log('set field ' + fldConfig.DomId + ' default value based on name url parameter');
14339                fld.SetValue(nameUrlParam);
14340
14341                if (_isGenericAmountInputField(fldConfig))
14342                    _urlAmount = nameUrlParam.toDecimalZeroIfInvalid();
14343
14344                //todo: add non-id mapping attempt
14345                if (_isPaymentCurrencyField(fldConfig))
14346                    _urlCurrencyId = nameUrlParam.toIntZeroIfInvalid();
14347
14348                //todo: add non-id mapping attempt
14349                if (_isRecurringFrequencyField(fldConfig))
14350                    _urlFrequencyId = nameUrlParam.toIntZeroIfInvalid();
14351
14352                //todo: add non-id mapping attempt
14353                if (_isPaymentMethodField(fldConfig))
14354                    _urlPaymentMethodId = nameUrlParam.toIntZeroIfInvalid();
14355
14356            }
14357                         
14358        }
14359
14360        //add special variables - e.g. amount, frequency, paymentmethod, currency
14361        let amountUrlParam = getURLParameterByName('amount');
14362        if (thisApi.FirstGenericAmountInputField != null && isBlank(_urlAmount) && !isBlank(amountUrlParam)) {
14363            _logger.Log('set payment amount field value from special url parameter');     
14364            thisApi.FirstGenericAmountInputField.SetValue(amountUrlParam);            
14365            _urlAmount = amountUrlParam.toDecimalZeroIfInvalid();
14366        }
14367
14368        _isUrlAmountProvided = (_urlAmount != null && _urlAmount > 0); 
14369
14370        let currencyIdUrlParam = getURLParameterByName('currencyId');
14371        if (thisApi.PaymentCurrencyField != null && isBlank(_urlCurrencyId) && !isBlank(currencyIdUrlParam)) { 
14372            _logger.Log('set currency field value from special url parameter');   
14373            thisApi.PaymentCurrencyField.SetValue(currencyIdUrlParam);            
14374            _urlCurrencyId = currencyIdUrlParam.toIntZeroIfInvalid();
14375        }
14376
14377        let recurringFrequencyIdUrlParam = getURLParameterByName('paymentFrequencyId');
14378        if (thisApi.RecurringPaymentFrequencyField != null && isBlank(_urlFrequencyId) && !isBlank(recurringFrequencyIdUrlParam)) { 
14379            _logger.Log('set frequency field value from special url parameter');                
14380            thisApi.RecurringPaymentFrequencyField.SetValue(recurringFrequencyIdUrlParam);               
14381            _urlFrequencyId = recurringFrequencyIdUrlParam.toIntZeroIfInvalid();
14382        }
14383
14384        let paymentMethodIdUrlParam = getURLParameterByName('paymentMethodId');
14385        if (thisApi.PaymentMethodField != null && isBlank(_urlPaymentMethodId) && !isBlank(paymentMethodIdUrlParam)) { 
14386            _logger.Log('set payment method field value from special url parameter');                
14387            thisApi.PaymentMethodField.SetValue(paymentMethodIdUrlParam);
14388            _urlPaymentMethodId = paymentMethodIdUrlParam.toIntZeroIfInvalid();
14389        }
14390
14391        _isUrlPaymentMethodProvided = (_urlPaymentMethodId != null && _urlPaymentMethodId.toIntZeroIfInvalid() > 0); 
14392
14393    };
14394
14395    const _handleContactPostalCodeLookup = function (result) {
14396
14397        //_logger.Log('postal code lookup: ' + JSON.stringify(result));  
14398        //_logger.Log('result.City: ' + result.City); 
14399        //_logger.Log('result.State_Id: ' + result.State_Id); 
14400        //_logger.Log('result.Country_Id: ' + result.Country_Id); 
14401
14402        let curCityVal = _tryGetFieldValue(thisApi.ContactAddressCityField); 
14403        if(curCityVal != result.Result.City)
14404            _trySetFieldValue(thisApi.ContactAddressCityField,result.Result.City);
14405
14406        let curStateVal = _tryGetFieldValue(thisApi.ContactAddressStateProvinceField);
14407        if (curStateVal != result.Result.State_Id)
14408            _trySetFieldValue(thisApi.ContactAddressStateProvinceField,result.Result.State_Id);                    
14409
14410        let countryVal = _tryGetFieldValue(thisApi.ContactAddressCountryField);
14411        if (result.Result.Country_Id > 0 && countryVal != result.Result.Country_Id)
14412            _trySetFieldValue(thisApi.ContactAddressCountryField,result.Result.Country_Id);
14413
14414    };  
14415
14416    const _handlePromoCodeValidated = function () {
14417
14418        _logger.Log('promo code validated, handle amount impacting change event');
14419        _updateFormAfterAmountImpactingChange();
14420    }
14421
14422    const _fireAndForgetPageViewAjaxCall = function () {
14423
14424        let params = {
14425            "form_id": thisApi.Form_Id,
14426            "trans_id": 0,
14427            "pageRequestUrl": window.location.href,                                    
14428            "cas_id": WebFormContext.Cas_Id,            
14429            "conversionHit_id": 0,
14430            "overrideRequestMethodId": 1
14431        };
14432         
14433        App.BackgroundPostJsonAsync('/api/WebFormsAppAjaxEndpoint/LogPageRequestAsync',
14434            JSON.stringify(params),
14435            function (result) { _logger.Log('async page request logged'); },
14436            function (error) { _logger.Log('error logging page request: ' + error); });
14437
14438    };
14439    
14440    let _isInitialized = false;
14441    let _initializeCompletedPreCallBacks = false;
14442
14443    const _getLoginModalElem = function () { return document.getElementById('formLoginModal'); };
14444    const _getLoginModalTopMessageElem = function () { return document.getElementById('formLoginModalTopErrorMessage'); }
14445    const _getLoginModalUsernameElem = function () { return document.getElementById('formLoginUserName'); }
14446    const _getLoginModalPassphraseElem = function () { return document.getElementById('formLoginPassphrase'); }
14447
14448    let _formLoginModal = null;
14449
14450    const _openFormLoginModal = function () {
14451
14452        var modalElem = _getLoginModalElem();
14453        if (modalElem != null)
14454            modalElem.show(); 
14455
14456    }
14457
14458    const _closeFormLoginModal = function () {
14459
14460        var modalElem = _getLoginModalElem();
14461        if (modalElem != null)
14462            modalElem.vanish();
14463
14464    }
14465
14466    const _addFormLoginModal = function () {
14467
14468        //Add DOM Elements if they do not exist
14469        let loginModal = _getLoginModalElem();
14470        if (loginModal == null) {
14471
14472            //create container div
14473            loginModal = createAnonymousDomElement('div', 'pageOverlays');
14474            loginModal.setAttribute('id', 'formLoginModal');
14475
14476            //add inner div
14477            let modalInnerDiv = createAnonymousDomElement('div', 'appModals');
14478
14479            //add header title
14480            let headerElem = createAnonymousDomElement('div', 'appModalHeaders');
14481
14482            let headerTitleTextElem = createAnonymousDomElement('span', 'appModalHeaderHeaderTitles');
14483
14484            headerTitleTextElem.innerHTML = 'Enter credentials to access site';
14485
14486            headerElem.appendChild(headerTitleTextElem); //headerTitle
14487
14488            modalInnerDiv.appendChild(headerElem);
14489
14490            //add content container div
14491            let modalUIContainer = createAnonymousDomElement('div', 'ce-formLoginContainer');
14492
14493            //add error message div
14494            let topErrorMessage = document.createElement('div');
14495            topErrorMessage.setAttribute('id', 'formLoginModalTopErrorMessage');
14496            topErrorMessage.vanish();
14497
14498            modalUIContainer.appendChild(topErrorMessage);
14499
14500            //add content
14501            let unContainer = createAnonymousDomElement('div', 'ce-formLoginRow');
14502
14503            let unLabel = createAnonymousDomElement('label', 'ce-formLoginFieldLabel', null, 'Username');
14504            unContainer.appendChild(unLabel);
14505
14506            let unInputDiv = createAnonymousDomElement('div', 'ce-formLoginFieldWrapper', null, '<input type="text" id="formLoginUserName" value="" class="ce-textBoxes ce-formLoginFields" placeholder="Username" aria-required="true" aria-invalid="false">')
14507            unContainer.appendChild(unInputDiv);
14508
14509            modalUIContainer.appendChild(unContainer);
14510
14511            let ppContainer = createAnonymousDomElement('div', 'ce-formLoginRow');
14512
14513            let ppLabel = createAnonymousDomElement('label', 'ce-formLoginFieldLabel', null, 'Passphrase');
14514            ppContainer.appendChild(ppLabel);
14515
14516            let ppInputDiv = createAnonymousDomElement('div', 'ce-formLoginFieldWrapper', null, '<input type="text" id="formLoginPassphrase" value="" class="ce-textBoxes ce-formLoginFields" placeholder="Passphrase" aria-required="true" aria-invalid="false">')
14517            ppContainer.appendChild(ppInputDiv);
14518
14519            modalUIContainer.appendChild(ppContainer);
14520
14521            modalInnerDiv.appendChild(modalUIContainer);
14522
14523            //add footer div
14524            let footerElem = createAnonymousDomElement('div', 'ce-formLoginFooter');
14525
14526            //add login button
14527            let buttonCssClass = 'popUpButtons ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only';
14528            
14529            let buttonLabelCssClasses = 'ui-button-text';
14530
14531            let processButton = createAnonymousDomElement('button', buttonCssClass, null, '<span class="' + buttonLabelCssClasses + '">Login</span>');
14532            processButton.setAttribute('type', 'button'); //i'm sure both of these are necessary for Jquery
14533            processButton.setAttribute('role', 'button'); //i'm sure both of these are necessary for Jquery
14534            processButton.setAttribute('aria-disabled', 'false');
14535            processButton.setAttribute('onclick', 'CE.APPS.WebForm.ProcessFormLogin();');
14536
14537            footerElem.appendChild(processButton);
14538
14539            modalInnerDiv.appendChild(footerElem);
14540
14541            loginModal.appendChild(modalInnerDiv);
14542
14543            document.body.appendChild(loginModal);
14544
14545            loginModal.vanish();
14546        }
14547
14548        _openFormLoginModal();
14549
14550    }
14551
14552    const _showFormLoginError = function (message) {
14553
14554        _getLoginModalTopMessageElem().innerHTML = '';
14555
14556        if (!isBlank(message)) {
14557
14558            _getLoginModalTopMessageElem().show();
14559           
14560            _getLoginModalTopMessageElem().addHtml(message, 'headerDynamicMessageContent');
14561
14562        }
14563        else
14564            _getLoginModalTopMessageElem().vanish();
14565    }
14566
14567    const _clearFormLoginError = function () {
14568        _getLoginModalTopMessageElem().innerHTML = '';
14569
14570        _getLoginModalTopMessageElem().vanish();
14571
14572    }
14573
14574    thisApi.ProcessFormLogin = function () {
14575
14576        _logger.Log('call WebFormLoginUser');
14577        _clearFormLoginError();
14578
14579        let usernameElem = _getLoginModalUsernameElem();
14580        let passphraseElem = _getLoginModalPassphraseElem();
14581
14582        if (usernameElem != null && passphraseElem != null) {
14583
14584            let requiredFieldsValidated = true;
14585
14586            usernameElem.classList.remove('error');
14587            passphraseElem.classList.remove('error');
14588
14589            if (usernameElem.getValue() == '' && requiredFieldsValidated) {
14590                requiredFieldsValidated = false;
14591                usernameElem.classList.add('error');
14592                _showFormLoginError('Username is required');
14593            }
14594
14595            if (passphraseElem.getValue() == '' && requiredFieldsValidated) {
14596                requiredFieldsValidated = false;
14597                passphraseElem.classList.add('error');
14598                _showFormLoginError('Passphrase is required');
14599            }
14600
14601            if (requiredFieldsValidated) {
14602
14603                App.PostJsonAsync('/api/PublicWebAppUIAjaxEndpoint/WebFormLoginUser',
14604                    JSON.stringify({ form_id: thisApi.Form_Id, username: usernameElem.getValue(), passphrase: passphraseElem.getValue() }),
14605                    function (result) {
14606
14607                        //console.log('process web form transaction response: ' + result);
14608
14609                        if (result.Success)
14610                            _closeFormLoginModal();
14611                        else
14612                            _showFormLoginError(result.ErrorMessage);
14613
14614                    },
14615                    function (error) {
14616
14617                        console.log('error: ' + error);
14618
14619                        _showFormLoginError(error);
14620
14621                    });
14622            }
14623        }
14624        else
14625            _showFormLoginError('Username and Passphrase are required'); 
14626    }
14627
14628    const _enableDynamicLabels = true;
14629
14630    const _initializeDynamicLabelField = function (domElement, fldLbl) {
14631         
14632        if (_enableDynamicLabels && domElement != null && fldLbl != null) {
14633
14634            const fldContainerElem = domElement.firstParentWithClass('ce-formFieldContainers');
14635            if(fldContainerElem != null) {
14636                 
14637                const fldType = domElement.getInputFieldType(true);
14638
14639                if (fldType == FormInputTypes.TextBox || fldType == FormInputTypes.Password || fldType == FormInputTypes.TextArea || fldType == FormInputTypes.DropDownList)
14640                    initializePublicAppFormDynamicLabel(domElement,fldLbl);
14641                     
14642            }
14643
14644        }
14645         
14646    };
14647
14648    const _initializeDynamicLabelFields = function () {
14649
14650        if (_enableDynamicLabels) {            
14651
14652            thisApi.FormElement.addClassName('ce-dynamicLabelsInitialized');
14653
14654            for (let x = 0; x < _allFieldApis.length; x++)
14655                if (_allFieldApis[x].LabelDomElement != null){
14656
14657                    let isAlwaysVisibleLabel = (_allFieldApis[x].Configuration.Type.Id == FORM_FIELD_TYPES.TICKET_QUANTITY_SYSTEM);    
14658
14659                    if (!isAlwaysVisibleLabel)
14660                        isAlwaysVisibleLabel = (_allFieldApis[x].LabelText != _allFieldApis[x].PlaceholderText);
14661
14662                    const lblElem = _allFieldApis[x].DomElement.getAssociatedFieldLabel();
14663
14664                    if (isAlwaysVisibleLabel && lblElem != null)                                                                 
14665                        lblElem.addClassName('ce-alwaysVisibleLabels');
14666 
14667                    _initializeDynamicLabelField(_allFieldApis[x].DomElement, lblElem);
14668
14669                }
14670
14671            _logger.Log('finished initializing standard field dynamic labels (' + _allFieldApis.length + ' fields)');
14672
14673        }
14674
14675    };
14676     
14677    thisApi.Initialize = function () {
14678
14679        if (!_isInitialized) { 
14680
14681            _logger.Log('initialize started');
14682
14683            //check for critical compatibility issues
14684            let isCriticalCompatibilityIssue = _checkForCriticalConfigurationIssues();            
14685
14686            if (!isCriticalCompatibilityIssue) {
14687
14688                if (thisApi.FormElement != null && thisApi.SubmitButtonElement != null) {
14689
14690                    _isInitialized = true;
14691                     
14692                    _setDefaultView();
14693
14694                    _setFormDefaultValuesFromUrl();
14695
14696                    thisApi.SubmitButtonElement.addEventListener('click', function () { _handleSubmitClick(); });
14697
14698                    _initializeDynamicLabelFields();
14699
14700                    //wire up interactive validation                     
14701                    for (let x = 0; x < _allFieldApis.length; x++)
14702                        _wireUpFieldValueChangeDetectionEvent(_allFieldApis[x].Configuration, _validateFieldElementRelative);
14703                       
14704                    //wire up all amount fields 
14705                    if (thisApi.GenericAmountInputFields.length > 0)
14706                        for (let x = 0; x < thisApi.GenericAmountInputFields.length; x++)
14707                            _wireUpFieldValueChangeDetectionEvent(thisApi.GenericAmountInputFields[x].Configuration, _handlePaymentAmountChanged);
14708
14709                    _initializePaymentAmountSection();
14710
14711                    //move the button container into place BEFORE Initialize() renders into it - reparenting
14712                    //the container after render() causes the PayPal SDK to tear down/destroy the buttons
14713                    _movePayPalButtonIntoPaymentMethodSection();
14714
14715                    //_logger.Log('initialize paypal API (' + (typeof thisApi.PayPalApi) + ')');
14716                    if (thisApi.PayPalApi != null && typeof thisApi.PayPalApi != 'undefined' && thisApi.PayPalApi.PayPalEnabled) {
14717                        _logger.Log('Initialize paypal native button');
14718
14719                        thisApi.PayPalApi.RegisterTokenReceivedCallBack(_handlePayPalTokenReceived);
14720                        thisApi.PayPalApi.RegisterPaymentAuthorizedCallBack(_handlePaypalPaymentAuthorized);
14721                        thisApi.PayPalApi.Initialize();  //{PayPalApiVariableName}.ShowButton(); //why do we have to show it?  Is this forms specific behavior?
14722                    }
14723
14724                    //_logger.Log('initialize applepay API (' + (typeof thisApi.ApplePayApi) + ')');
14725                    if (thisApi.ApplePayApi != null && typeof thisApi.ApplePayApi != 'undefined' && thisApi.ApplePayApi.ApplePayEnabled) {
14726                        _logger.Log('Initialize applepay native button');
14727                        thisApi.ApplePayApi.RegisterPaymentAuthorizedCallBack(_handleApplePayPaymentAuthorized);
14728                        thisApi.ApplePayApi.Initialize();
14729                    }
14730
14731                    //handled in script loaded
14732
14733                    //_logger.Log('initialize dafpay API (' + (typeof thisApi.DafPayApi) + ')');
14734                    if (thisApi.DafPayApi != null && typeof thisApi.DafPayApi != 'undefined' && thisApi.DafPayApi.DafPayEnabled) {
14735                        _logger.Log('Initialize dafpay native button');
14736                        thisApi.DafPayApi.RegisterButtonClickedCallBack(function (cbParams) { _handleDafPayButtonClicked(cbParams); });
14737                        thisApi.DafPayApi.RegisterPaymentAuthorizedCallBack(function (cbParams) { _handleDafPayPaymentAuthorized(cbParams); });
14738                        thisApi.DafPayApi.RegisterPaymentFailedCallBack(function (cbParams) { _handleDafPayPaymentAuthorizationFailed(cbParams); });
14739                        thisApi.DafPayApi.Initialize();
14740                    }
14741
14742                    _initializePaymentMethodSection();
14743
14744                    _initializeMembershipSection();
14745                     
14746                    _initializePledgeSection();
14747
14748                    _initializeContactInfoSection();
14749
14750                    _initializeTributeSection();
14751
14752                    _initializeEventTicketsSection();
14753
14754                    _initializeProducts();
14755
14756                    _initializeVolunteerPostTimeEntryWidget();
14757
14758                    _initializeFileUploadFields();                                        
14759
14760                    //_refreshFreeGiftSectionBasedOnAmountChange(); //called automatically
14761
14762                    _initializeDoubleTheDonation();                    
14763
14764                    //get visible ask strings if present and simulate click to initalize amount and UI
14765                    if (!_isUrlAmountProvided)
14766                        _simulateVisibleAskBlockClick();
14767                    else
14768                        _logger.Log('skip startup simulate ask click because of amount url override');
14769
14770                    if (thisApi.PaymentMethodField != null) {
14771
14772                        //set default form values (config, url, business rules
14773                        let defPaymentMethodId = thisApi.PaymentMethodField.DefaultIntegerValue;
14774                        if (defPaymentMethodId <= 0)
14775                            defPaymentMethodId = PAYMENT_METHODS.CREDIT_CARD;
14776
14777                        if (_isUrlPaymentMethodProvided)
14778                            defPaymentMethodId = _urlPaymentMethodId;
14779
14780                        thisApi.PaymentMethodField.SetValue(defPaymentMethodId);
14781
14782                    }
14783                    
14784                    //wire up promo code API (todo: add promo code if no field?)
14785                    if (thisApi.PromoCodeField != null) {
14786                        thisApi.PromoCodeFieldApi = getPromoCodeFormFieldApi({ "AutoInitialize": false, "PromoEntryTextBoxDomId": thisApi.PromoCodeField.Configuration.DomId, "GetTotalAmountFunction": function () { return thisApi.GetPromoCodeApplicableTotalAmount(); }, "GetSelectedTicketQuantitiesFunction": function () { return thisApi.GetTicketQuantities(); } });
14787                        thisApi.PromoCodeFieldApi.Initialize();
14788                        thisApi.PromoCodeFieldApi.RegisterValidationSuccessfulChangedCallBack(_handlePromoCodeValidated);
14789                    }
14790
14791                    //wire up postal code
14792                    if (thisApi.ContactAddressPostalCodeField != null) {
14793                        //NOTE: relaying on event call back instead of field mapping so we can use internal APIs for cascading functionality - i.e., SetValue of city triggers future client event listener
14794                        thisApi.ContactPostalCodeFieldApi = getPostalCodeLookupFormFieldApi({ "AutoInitialize": false, "PostalCodeEntryTextBoxDomId": thisApi.ContactAddressPostalCodeField.Configuration.DomId });
14795                        thisApi.ContactPostalCodeFieldApi.RegisterLookupSuccessfulCallBack(_handleContactPostalCodeLookup);
14796                        thisApi.ContactPostalCodeFieldApi.Initialize();
14797                    }
14798
14799                    //NOTE: google maps gets injected in via script load event (calls google maps script loaded initializer)
14800                    //      thisApi.HandleGoogleMapsScriptLoaded();
14801
14802                    //TODO: this is already in context, just use if not returned from identify visitor
14803                    //just need getters.
14804                    _initializeVisitorAnonymousContextInfo();
14805
14806                    //DEBUGGING (COMMENT to SPEED UP DEBUGGING - REMOVE COMMENT BEFORE DEPLOYING)
14807                    //DEBUGGING (COMMENT to SPEED UP DEBUGGING - REMOVE COMMENT BEFORE DEPLOYING)
14808                    //DEBUGGING (COMMENT to SPEED UP DEBUGGING - REMOVE COMMENT BEFORE DEPLOYING)
14809
14810                    //allow disabling
14811                    if(WebFormContext.Form.EnableContactIdentification)
14812                        _initializeKnownVisitorContext();                    
14813                        
14814                    if (thisApi.PaymentMethodField != null) thisApi.PaymentMethodField.HideOptionByValue(PAYMENT_METHODS.ONECLICK_DONATION); //hide one click in the payment methods no matter what (it is fake)
14815
14816                    if (!_payPalPaymentMethodAvailable())
14817                        thisApi.PayPalApi.HideButton();
14818
14819                    if (!_creditCardPaymentMethodAvailable()) {
14820                        _tryHideGooglePayButton();
14821                        _tryHideApplePayButton();
14822                    }
14823
14824                    if (!thisApi.DafPayPaymentMethodAvailable)
14825                        thisApi.DafPayApi.HideButton();
14826
14827                    //debugging (force one-click on)
14828                    //******************************
14829                    //_oneClickOptContactIn(1); //_initializeOneClickCheckout(1, 2, 'credit card that ends with 5011');                    
14830                    //******************************
14831
14832                    _updateFormAfterAmountImpactingChange();
14833
14834                    if (WebFormContext.Form.RequireLogin)
14835                        _addFormLoginModal();
14836
14837                    //wireup form ready event (IMPORTANT: need theme to comply with calling new intialized event or race conditions can occur - _isThemeInitialized)
14838                    if (thisApi.ThemeRunning && !_isThemeInitialized) {
14839
14840                        _logger.Log('register theme initialized call back to delegate ready to theme');
14841                        thisApi.RegisterThemeInitializedCallBack(function () { _fireFormReadyEvent(); });
14842
14843                    }
14844                    else {
14845
14846                        _logger.Log('fire ready event (no theme or theme initialized)');
14847                        _fireFormReadyEvent();
14848
14849                    }
14850
14851                    _fireAndForgetPageViewAjaxCall();
14852
14853                    _initializeCompletedPreCallBacks = true;
14854
14855                    _fireFormInitializedEvent(); //fire custom events after ready (users should use ready, not initialized)
14856
14857                }
14858                else
14859                    _logger.Log('initialization failed (form element and submit button cannot be found)');
14860
14861            }
14862            else
14863            { 
14864
14865                //TODO: we still need to consider the idea that there's one mapped payment field - might be solid, might not
14866                _logger.Log('initialization failed (configuration - check warning above and fix form settings)');
14867
14868                let errorMessage = 'Form is not confingured properly<br />';
14869
14870                for (let x = 0; x < _criticalConfigurationMessages.length; x++)
14871                    errorMessage += '<br />' + _criticalConfigurationMessages[x];
14872
14873                _setErrorView('Critical Configuration Error', errorMessage);
14874
14875                _hideAllSections();
14876
14877                _hideAllPageContentElements();
14878
14879                thisApi.HideSubmitButton();
14880
14881            }
14882
14883        }
14884        else
14885            _logger.Log('initialization skipped (already initialized)');
14886
14887    }        
14888
14889    if (thisApi.AutoInitialize) {
14890    
14891        document.addEventListener('DOMContentLoaded', function () { thisApi.Initialize(); });
14892    
14893    }
14894
14895    return thisApi;
14896
14897}
14898
14899
14900/*App Core UI Apis*/
14901
14902function getCeAppsWebFormsApi() {
14903    
14904    let thisApi = {};
14905
14906    const _logger = getConsoleLogApi({ "LogSourceName": 'CE Apps Web Form Api' });
14907
14908    const _webFormApis = [];
14909
14910    const _isFormApiRegistered = function (api) {
14911
14912        let retVal = false;
14913
14914        for (let x = 0; x < _webFormApis.length; x++)
14915            if (_webFormApis[x].Form_Id == api.Form_Id) {
14916                retVal = true;
14917                break;
14918            }
14919
14920        return retVal;
14921
14922    };
14923
14924    thisApi.RegisterFormApi = function (api) {
14925
14926        if (!_isFormApiRegistered(api))
14927            _webFormApis.push(api);
14928        else
14929            _logger.Log('web form ' + api.Form_Id + ' already registered');
14930
14931    };
14932
14933    thisApi.GetFormApi = function () {
14934
14935        let retVal = null;
14936
14937        if (_webFormApis.length > 1)
14938            _logger.Log('WARNING: Web Form Api referenced with form when more than one form present on page (use GetWebFormById to get a specific form)');
14939
14940        _logger.Log('get web form API: ' + _webFormApis.length);
14941        if (_webFormApis.length > 0)
14942            retVal = _webFormApis[0];
14943
14944        return retVal;
14945
14946    }; //gets first form
14947
14948    //simulate property for function with no parameters
14949    Object.defineProperty(thisApi, 'Form', {
14950        get: function() {
14951            return thisApi.GetFormApi();
14952        }
14953    });
14954
14955    thisApi.GetFormApiById = function(form_id) {
14956
14957        let retVal = null;
14958
14959        for (let x = 0; x < _webFormApis.length; x++)
14960            if (_webFormApis[x].Form_Id == form_id) {
14961                retVal = _webFormApis[x];
14962                break;
14963            }
14964
14965        return retVal;
14966
14967    };
14968
14969    thisApi.InitializeAll = function() {
14970         
14971        for (let x = 0; x < _webFormApis.length; x++)
14972            _webFormApis[x].Initialize(); 
14973
14974    };
14975
14976    return thisApi;
14977
14978}
14979
14980function getCeAppsApi() {
14981    
14982    let thisApi = {};
14983
14984    const _logger = getConsoleLogApi({ "LogSourceName": 'CE Apps Api' });
14985
14986    thisApi.WebForms = getCeAppsWebFormsApi();
14987
14988    //daisy-chained getter for single form implementations (UX)
14989    Object.defineProperty(thisApi, 'WebForm', {
14990        get: function() {
14991            return thisApi.WebForms.Form;
14992        }
14993    });
14994
14995    thisApi.ProcessWebFormTransaction = function (transactionPayLoad, successCallBack, errorCallBack) {
14996
14997        _logger.Log('call ProcessWebFormTransaction');
14998
14999        App.PostJsonAsync('/api/PublicWebAppUIAjaxEndpoint/ProcessWebFormTransaction',
15000            JSON.stringify(transactionPayLoad),
15001            function (result) {
15002
15003                //console.log('process web form transaction response: ' + result);
15004
15005                if (typeof successCallBack == 'function')
15006                    successCallBack(result);
15007  
15008            },
15009            function (error) {
15010             
15011                console.log('error: ' + error);
15012
15013                if (typeof errorCallBack == 'function')
15014                    errorCallBack(error);
15015
15016            });
15017
15018    };
15019
15020    thisApi.Initialize = function () {
15021
15022        _logger.Log('initialize started');
15023
15024        thisApi.WebForms.InitializeAll();
15025
15026    };
15027
15028    return thisApi;
15029    
15030}
15031
15032function getCeApi() {
15033    
15034    const thisApi = {};
15035
15036    thisApi.APPS = getCeAppsApi();
15037
15038    //case-insensitive getter
15039    Object.defineProperty(thisApi, 'Apps', {
15040        get: function() {
15041            return thisApi.APPS;
15042        }
15043    });
15044     
15045    return thisApi;
15046
15047} 
15048
15049function getNavigationApi(config) {
15050
15051    const thisApi = {};
15052
15053    let _applicationType_id = AppTypes.Unknown;
15054    let _applicationEnvironment_id = AppEnvironments.Prod;
15055
15056    if (config != null) {
15057
15058        if (config.ApplicationType_Id != null)
15059            _applicationType_id = config.ApplicationType_Id;
15060
15061        if (config.ApplicationEnvironment_Id != null)
15062            _applicationEnvironment_id = config.ApplicationEnvironment_Id;
15063
15064    }
15065
15066    const _logger = getConsoleLogApi({ "LogSourceName": 'CE App Navigation Api' }); 
15067
15068    const _getCrmBaseUrl = function () {
15069
15070        let baseUrl = 'https://admin.charityengine.net';
15071
15072        switch (_applicationEnvironment_id) {
15073            case AppEnvironments.Dev:
15074                baseUrl = 'https://localhost:44355';
15075                break;
15076        }
15077         
15078        return baseUrl;
15079
15080    };
15081    
15082    thisApi.GoToUrl = function(url, suppressProcessingMessage){
15083    
15084        if (!suppressProcessingMessage)
15085            App.UI.ProcessingOverlay.Show();
15086
15087        window.location.href = url;
15088
15089    };
15090
15091    thisApi.ReloadCurrentPage = function (suppressProcessingMessage) { thisApi.GoToUrl(window.location.href, suppressProcessingMessage); };
15092
15093    const _tryOpenUrl = function (url) {
15094
15095        if (_applicationType_id == AppTypes.OutlookConnector || _applicationType_id == AppTypes.OutlookConnector) {
15096
15097            try {
15098
15099                Office.context.ui.openBrowserWindow(url);
15100
15101            }
15102            catch (e) {
15103
15104                _logger.Log('unable to open url ' + url + ' using office add-in api - fail back to native: ' + e);
15105
15106                openUrlInNewBrowserTab(url);
15107            }
15108
15109        }
15110        else
15111            openUrlInNewBrowserTab(url);
15112
15113    };
15114
15115    thisApi.OpenCrmActivity = function (activity_id) {
15116        _tryOpenUrl(_getCrmBaseUrl() + '/dsp_crm_contactActivity.aspx?activityid=' + activity_id);
15117    };
15118
15119    thisApi.OpenCrmTransaction = function (trasaction_id) {
15120        _tryOpenUrl(_getCrmBaseUrl() + '/dsp_ts_transaction.aspx?tid=' + trasaction_id);
15121    };
15122
15123    thisApi.OpenCrmContact = function (contact_id) {
15124        _tryOpenUrl(_getCrmBaseUrl() + '/dsp_crm_contact.aspx?contid=' + contact_id);
15125    };
15126
15127    thisApi.OpenCrmNote = function (note_id) {
15128        _tryOpenUrl(_getCrmBaseUrl() + '/dsp_crm_contactNotes.aspx?notesid=' + note_id);
15129    };
15130
15131    thisApi.OpenQuery = function (query_id) {
15132        _tryOpenUrl(_getCrmBaseUrl() + '/dsp_ce_queryBuilder.aspx?reportID=' + query_id);
15133    };
15134
15135    thisApi.OpenCase = function (case_id) {
15136        _tryOpenUrl(_getCrmBaseUrl() + '/dsp_sc_ticket.aspx?sc_tkid=' + case_id);
15137    };
15138
15139    thisApi.OpenOpportunity = function (opportunity_id) {
15140        _tryOpenUrl(_getCrmBaseUrl() + '/dsp_crm_opportunity.aspx?opid=' + opportunity_id);
15141    };
15142     
15143    return thisApi;
15144
15145}
15146
15147function getUIControlsApi(config) {
15148
15149    const thisApi = {};
15150     
15151    let _logger = getConsoleLogApi({ "LogSourceName": 'CE UI Controls Api' });
15152
15153    thisApi.CreateFormRowElement = function(domId, classes) {
15154     
15155        let classVal = 'formRows';
15156
15157        if (!isBlank(classes))
15158            classVal += ' ' + classes;
15159
15160        return createDomElement('div', domId, classVal);
15161     
15162    };
15163
15164    thisApi.CreateCenteredFormRowElement = function(domId, classes) {
15165     
15166        return thisApi.CreateFormRowElement(domId, 'centered');
15167     
15168    };
15169
15170    thisApi.CreateLink = function(text, url, newTab, domId, classes, tooltip) {    
15171        return createHyperLink(text, url, classes, null, tooltip, domId, newTab);
15172    };
15173
15174    thisApi.CreateJavascriptLink = function (text, javascriptFunction, domId, classes, tooltip) {
15175
15176        const hl = createHyperLink(text, 'javascript:void(0)', classes, null, tooltip, domId);
15177
15178        hl.addEventListener('click', function () { javascriptFunction(); });
15179
15180        return hl;
15181
15182    };
15183
15184    thisApi.AddSingleFieldFormRow = function(form, newElem, labelHtml, rowDomId, rowClasses) {
15185
15186        const newRow = thisApi.CreateFormRowElement(rowDomId, rowClasses);
15187
15188        if (!isBlank(labelHtml)) 
15189            newRow.addFormFieldLabel(newElem.id, labelHtml);    
15190
15191        newRow.appendChild(newElem);
15192
15193        form.appendChild(newRow);
15194
15195    };
15196
15197    thisApi.AddSingleFieldCenteredFormRow = function(form, newElem, rowDomId, rowClasses) {
15198
15199        let rowClassVal = 'centered';
15200
15201        if (!isBlank(rowClasses))
15202            rowClassVal += ' ' + rowClasses;
15203
15204        return thisApi.AddSingleFieldFormRow(form, newElem, null, rowDomId, rowClassVal);
15205
15206    };
15207
15208    const _initializeToggleOnOffCheckBox = function (checkBoxElem) {
15209
15210        if (checkBoxElem != null && !checkBoxElem.hasParentWithClass('toggleOnOffCheckBoxes')) {
15211
15212            const checkboxDomId = checkBoxElem.id;
15213
15214            if (!isBlank(checkBoxElem.id)) {
15215
15216                const newWrap = checkBoxElem.wrapElementInNewElement('div', 'toggleOnOffCheckBoxes');
15217
15218                if (newWrap != null) {
15219
15220                    const lbl = document.createElement('label');
15221                    lbl.setAttribute('for', checkBoxElem.id);
15222
15223                    newWrap.appendChild(lbl);
15224
15225                }
15226                else
15227                    _logger.Log('failed to initialize on/off toggle checkbox (critical dependency failed)');
15228
15229            }
15230            else
15231                _logger.Log('failed to initialize on/off toggle checkbox (dom id requried)');
15232
15233        }
15234
15235    };
15236
15237    const _initializeToggleOnOffCheckBoxes = function () {
15238
15239        //TODO: might want paramter or configuration to scope document to app UI
15240        const cbxLst = document.querySelectorAll('input[type="checkbox"]');
15241        for (let x = 0; x < cbxLst.length; x++) {
15242
15243            const cb = cbxLst[x];
15244
15245            if (cb.parentNode != null && !cb.hasClassName('noToggle') && !cb.hasParentWithClass('noToggle')) { 
15246
15247                //check to make sure its a single checkbox (we don't want to turn a seris of 5 checkbox/labels into toggle - think check all the options that apply)
15248                let parentCheckboxCount = cb.parentNode.querySelectorAll('input[type="checkbox"]').length
15249
15250                let grandParentCheckboxCount = 0; //belt and suspenders for div wrapped.
15251                
15252                if (cb.parentNode.parentNode != null)
15253                    grandParentCheckboxCount = cb.parentNode.parentNode.querySelectorAll('input[type="checkbox"]').length;
15254
15255                if (parentCheckboxCount == 1 && (cb.parentNode.hasClassName('formRows') || grandParentCheckboxCount == 1))
15256                    _initializeToggleOnOffCheckBox(cb);
15257
15258            }
15259
15260        }
15261
15262    };
15263
15264    //Initialize
15265    thisApi.Initialize = function () {
15266
15267        _logger.Log('initialize started');
15268
15269        _initializeToggleOnOffCheckBoxes();
15270
15271    };
15272
15273    return thisApi;
15274
15275}
15276
15277function getUIValidationApi(config) {
15278
15279    const thisApi = {};
15280     
15281    let _logger = getConsoleLogApi({ "LogSourceName": 'CE UI Validation Api' });
15282
15283    thisApi.ValidateForm = function (formElementOrSection) {
15284
15285        App.UI.Messages.ClearMessages();
15286
15287        const validationResp = validateFormFields(formElementOrSection);        
15288
15289        const validated = (validationResp.InvalidFields.length <= 0);
15290
15291        if (!validated) {
15292
15293            let errMess = '';
15294             
15295            if (validationResp.InvalidFields.length == 1) {
15296
15297                const invalidFld = validationResp.InvalidFields[0];
15298
15299                errMess = getFieldValidationMessageOrLabel(invalidFld);
15300
15301                if (invalidFld.id == 'setupNewAccountPassword') {
15302
15303                    const valResp = evaluateUserPassword(invalidFld.getValue());
15304                    errMess = valResp.Reason;
15305
15306                }
15307
15308            }
15309             
15310            if (isBlank(errMess))
15311                errMess = 'Review all of your information for errors';                    
15312
15313            App.UI.Messages.SetError(errMess);
15314
15315        }
15316
15317        return validated;
15318     
15319    };
15320      
15321    return thisApi;
15322
15323} 
15324
15325function getDistributedAppApi(config) {
15326
15327    const thisApi = {};
15328
15329    thisApi.UI = {};
15330
15331    thisApi.LocalStorage = getLocalStorageApi();
15332     
15333    thisApi.Currencies = getCurrencyApi();
15334
15335    thisApi.Countries = getCountriesApi();
15336    thisApi.StateProvinces = getStateProvincesApi();
15337
15338    let uiMessagesElementDomId = config.MainContainerElementDomId;
15339
15340    //allow override
15341    if (!isBlank(config.UserMessagesContainerElementDomId))
15342        uiMessagesElementDomId = config.UserMessagesContainerElementDomId;
15343
15344    thisApi.UI.Messages = getAppUIUserMessagesApi({ "MainContainerElementDomId": uiMessagesElementDomId });    
15345
15346    thisApi.UI.ConfirmActionPopUp = getAppUIConfirmActionPopUpApi();
15347
15348    thisApi.UI.ProcessingOverlay = getAppUIProcessingOverlayApi();
15349
15350    thisApi.UI.Controls = getUIControlsApi();
15351
15352    thisApi.Navigation = getNavigationApi({ ApplicationType_Id: config.ApplicationType_Id, ApplicationEnvironment_Id: config.ApplicationEnvironment_Id });
15353
15354    thisApi.Initialize = function () {
15355         
15356        thisApi.UI.Messages.Initialize();
15357        thisApi.UI.ConfirmActionPopUp.Initialize();
15358        thisApi.UI.ProcessingOverlay.Initialize();
15359        thisApi.UI.Controls.Initialize();
15360
15361        //TODO: add ready event for apps to listen to instead of DOMLoad
15362        if (typeof config.InitializedCallBack == 'function')
15363            config.InitializedCallBack();
15364
15365    };
15366
15367    return thisApi;
15368
15369}
15370
15371function getAppApi(config) {
15372
15373    const thisApi = {};
15374
15375    const _logger = getConsoleLogApi({ "LogSourceName": 'CE App Api' });   
15376
15377    thisApi.UI = {};
15378
15379    thisApi.WebAppVisitorContextHeaderKeyName = config.WebAppVisitorContextHeaderKeyName;
15380     
15381    thisApi.LocalStorage = getLocalStorageApi();
15382
15383    if(config.RequestValidationConfig != null)
15384        thisApi.RequestValidation = getRequestValidationApi(config.RequestValidationConfig);
15385    else
15386        _logger.Log('Request validation config not present (dependent functions will not work)');
15387
15388    if (config.CaptchaConfig != null)
15389        thisApi.Captcha = getGoogleCaptchaApi(config.CaptchaConfig);
15390    else
15391        _logger.Log('Captcha config not present (dependent functions will not work)');
15392
15393    thisApi.Currencies = getCurrencyApi();
15394
15395    thisApi.Countries = getCountriesApi();
15396    thisApi.StateProvinces = getStateProvincesApi();
15397
15398    thisApi.InKindTypes = getInKindTypesApi();
15399
15400    let uiMessagesElementDomId = config.MainContainerElementDomId;
15401
15402    //allow override
15403    if (!isBlank(config.UserMessagesContainerElementDomId))
15404        uiMessagesElementDomId = config.UserMessagesContainerElementDomId;
15405
15406    thisApi.UI.Messages = getAppUIUserMessagesApi({"MainContainerElementDomId": uiMessagesElementDomId});
15407
15408    thisApi.UI.Validation = getUIValidationApi();
15409
15410    thisApi.UI.ProcessingOverlay = getAppUIProcessingOverlayApi();
15411
15412    thisApi.Navigation = getNavigationApi();
15413
15414    thisApi.UI.CaptchaMfaModal = getCaptchaMfaModalApi();
15415     
15416    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
15417         
15418        CallBackManager.add('appApiInitializedCallBack', callBackFunction, null);
15419
15420    };
15421
15422    const _fireInitializedEvent = function () {
15423         
15424        CallBackManager.fire('appApiInitializedCallBack', {}, null);
15425
15426    };
15427
15428    const _isNonNativeAppPostAjaxSecurityProtocolUrl = function(url) {
15429        return url.containsValue('/GenerateRequestToken');
15430    };
15431
15432    const _tryRefreshAppToken = function(successCallBack) {
15433
15434        if (typeof App != 'undefined' && typeof App.RequestValidation != 'undefined')
15435            App.RequestValidation.RefreshToken(successCallBack, successCallBack); //RefreshToken tries to hide processing message (passing both call backs - success + error becuase we just want to know its done)
15436        else {
15437
15438            if (typeof successCallBack == 'function')
15439                successCallBack();
15440
15441        }
15442
15443    };
15444
15445    const _tryRefreshCaptchaToken = function(successCallBack) {
15446
15447        if (typeof App != 'undefined' && typeof App.Captcha != 'undefined')
15448            App.Captcha.RefreshCaptchaToken(successCallBack, successCallBack); //RefreshCaptchaToken tries to hide processing message (passing both call backs - success + error becuase we just want to know its done)
15449        else {
15450             
15451            if (typeof successCallBack == 'function')
15452                successCallBack();
15453
15454        }
15455
15456    };
15457
15458    const _handleNativeAppPostAjaxRequestSecurityProtocols = function(successCallBack) {
15459
15460        _tryRefreshAppToken(
15461            function () {
15462
15463                _tryRefreshCaptchaToken(function () {
15464                    
15465                    hideProcessingMessage();  //always (no harm)
15466
15467                    if (typeof successCallBack == 'function')
15468                        successCallBack();
15469
15470                });
15471
15472            });
15473 
15474    }
15475     
15476    thisApi.GetUrlAsync = function (url, successCallback, failureCallback) {
15477         
15478        showProcessingMessage();
15479
15480        getUrlWithSessionAsync(url, 
15481            function (result) {
15482                 
15483                let isBaseSecurityUrl = _isNonNativeAppPostAjaxSecurityProtocolUrl(url);
15484                if (!isBaseSecurityUrl)
15485                    _handleNativeAppPostAjaxRequestSecurityProtocols(function () { if(typeof successCallback == 'function') successCallback(result); });
15486                else
15487                    successCallback(result);
15488
15489            },
15490            function (error) {
15491                 
15492                let isBaseSecurityUrl = _isNonNativeAppPostAjaxSecurityProtocolUrl(url);
15493                if (!isBaseSecurityUrl)
15494                    _handleNativeAppPostAjaxRequestSecurityProtocols(function () {  if(typeof failureCallback == 'function') failureCallback(error); });
15495                else
15496                    failureCallback(error); //todo: need to parse WCF responses similar to success
15497
15498            });
15499
15500    };
15501
15502    thisApi.ProcessAjaxWithCaptchaFailover = function (ajaxFunction, captchaFailedPhoneNumber, 
15502captchaFailedEmail, captchaFailedButtonText) {
15503
15504        showProcessingMessage();
15505
15506        getUrlWithSessionAsync('/api/PublicWebAppUIAjaxEndpoint/ValidateCaptcha',
15507            function (result) {
15508
15509                //console.log('ValidateCaptcha response: ' + JSON.stringify(result));
15510
15511                _handleNativeAppPostAjaxRequestSecurityProtocols(
15512                    function () {
15513                         
15514                        if (result.Success)
15515                            ajaxFunction();
15516                        else if (result.CaptchaFailed) {
15517
15518                            App.UI.CaptchaMfaModal.Open(function () { ajaxFunction(); });
15519
15520                            //only set if null to allow blank to disable
15521                            if (captchaFailedPhoneNumber == null)
15522                                captchaFailedPhoneNumber = App.LocalStorage.GetValue('captcha_mfa_last_phone_number');                            
15523
15524                            if (!isBlank(captchaFailedPhoneNumber))
15525                                App.UI.CaptchaMfaModal.SetPhoneNumber(captchaFailedPhoneNumber);
15526
15527                            //only set if null to allow blank to disable
15528                            if (captchaFailedEmail == null)
15529                                captchaFailedEmail = App.LocalStorage.GetValue('captcha_mfa_last_email_address');
15530
15531                            if (!isBlank(captchaFailedEmail))
15532                                App.UI.CaptchaMfaModal.SetEmail(captchaFailedEmail);
15533
15534                            if (!isBlank(captchaFailedPhoneNumber) || isBlank(captchaFailedEmail))
15535                                App.UI.CaptchaMfaModal.SelectPhone();
15536                            else 
15537                                App.UI.CaptchaMfaModal.SelectEmail();
15538                            
15539                            App.UI.CaptchaMfaModal.SetSendCodeButtonText(captchaFailedButtonText); //defaults to complete transaction if blank
15540
15541                            if (!isBlank(captchaFailedPhoneNumber) || !isBlank(captchaFailedEmail))
15542                                App.UI.CaptchaMfaModal.RequestCode();
15543
15544                        }
15545                        else
15546                            _logger.Log('validate captcha failed with non captcha error (make sure app is configured properly!!!)');
15547
15548                    });
15549                     
15550            },
15551            function (error) {
15552                _handleNativeAppPostAjaxRequestSecurityProtocols();                
15553                _logger.Log('validate captcha failed with error: ' + error);                               
15554            });
15555
15556    };
15557
15558    thisApi.PostJsonAsync = function (url, data, successCallback, failureCallback, dontShowProcessingMessage) {
15559
15560        if (!dontShowProcessingMessage)  
15561            showProcessingMessage();
15562
15563        //temporary solution to test porting to webapi from WCF
15564        //NOTE: WCF eloquently handles ajaxParams --> complex ajax / WebApi requires top-level object, not embedded
15565        data = tryParseAjaxJsonPayloadData(data, url);
15566
15567        postJsonDataWithSessionAsync(url, data, 
15568            function (result) {
15569
15570                let isBaseSecurityUrl = _isNonNativeAppPostAjaxSecurityProtocolUrl(url);
15571                if (!isBaseSecurityUrl)
15572                    _handleNativeAppPostAjaxRequestSecurityProtocols(function () { if(typeof successCallback == 'function') successCallback(result); });
15573                else
15574                    successCallback(result);
15575
15576            },
15577            function (httpResponseObject, requestUrl) {
15578
15579                _logger.Log('critical error in PostJsonAsync (' + requestUrl + '):' + httpResponseObject);
15580
15581                if(typeof failureCallback != 'function')
15582                    thisApi.UI.Messages.SetError('critical error in PostJsonAsync (see log for details)');
15583
15584                let isBaseSecurityUrl = _isNonNativeAppPostAjaxSecurityProtocolUrl(url);
15585                if (!isBaseSecurityUrl)
15586                    _handleNativeAppPostAjaxRequestSecurityProtocols(function () {  if(typeof failureCallback == 'function') failureCallback(httpResponseObject); });
15587                else
15588                    failureCallback(httpResponseObject); //todo: need to parse WCF responses similar to success
15589
15590            });
15591             
15592    };
15593
15594    thisApi.BackgroundPostJsonAsync = function (url, data, successCallback, failureCallback) {
15595
15596        return thisApi.PostJsonAsync(url, data, successCallback, failureCallback, true);
15597         
15598    };
15599
15600    thisApi.Initialize = function () {
15601
15602        _logger.Log('initialize started');
15603
15604        thisApi.UI.Messages.Initialize();
15605        thisApi.UI.ProcessingOverlay.Initialize();
15606
15607        if (typeof thisApi.RequestValidation != 'undefined')
15608            thisApi.RequestValidation.Initialize();
15609
15610        if (typeof grecaptcha != 'undefined')
15611            grecaptcha.ready(function () { thisApi.Captcha.Initialize(); });
15612        else
15613            _logger.Log('App Api initialization skipping google captcha because third-party api is not available');
15614
15615        //TODO: add ready event for apps to listen to instead of DOMLoad
15616        if (typeof config.InitializedCallBack == 'function')
15617            config.InitializedCallBack();
15618
15619        _fireInitializedEvent();
15620
15621    };
15622
15623    return thisApi;
15624
15625}
15626
15627function getAppUIModalApi(config) {
15628
15629    const apiObj = {};
15630
15631    apiObj.ModalContainerElementId = '';     
15632      
15633    if(!isBlank(config.ModalContainerElementId))
15634        apiObj.ModalContainerElementId = config.ModalContainerElementId;     
15635  
15636    //core api helpers
15637    apiObj.GetContainerElement = function () { return document.getElementById(apiObj.ModalContainerElementId); };
15638
15639    apiObj.GetInnerContainerElement = function () {
15640
15641        let retVal = null;
15642
15643        let rootElem = this.GetContainerElement();
15644        if (rootElem != null && rootElem.firstElementChild != null && rootElem.firstElementChild.hasClassName('appModals')) 
15645            retVal = rootElem.firstElementChild;
15646
15647        return retVal;
15648
15649    };
15650
15651    let _getModalPrimaryContainerByClassName = function (className) {
15652
15653        let retVal = null;
15654
15655        if (!isBlank(className)) {
15656
15657            //must be direct descendent to avoid nested control 
15658            let innerContainer = apiObj.GetInnerContainerElement();        
15659            if (innerContainer != null && innerContainer.children.length > 0) 
15660                for (let x = 0; x < innerContainer.children.length; x++)                 
15661                    if (innerContainer.children[x].hasClassName(className)) { 
15662                        retVal = innerContainer.children[x];
15663                        break;
15664                    }    
15665
15666        }
15667
15668        return retVal;
15669
15670    };
15671
15672    apiObj.GetHeaderElement = function () {
15673
15674        let retVal = _getModalPrimaryContainerByClassName('appModalHeaders'); //must be direct descendent to avoid nested control 
15675         
15676        return retVal;
15677
15678    };
15679
15680    apiObj.GetMainUIElement = function () {
15681
15682        let retVal = _getModalPrimaryContainerByClassName('appModalUIContainers'); //must be direct descendent to avoid nested control          
15683
15684        return retVal;
15685
15686    };
15687
15688    apiObj.ShowMainUI = function () { if (apiObj.GetMainUIElement() != null) apiObj.GetMainUIElement().show(); };
15689    apiObj.HideMainUI = function () { if (apiObj.GetMainUIElement() != null) apiObj.GetMainUIElement().vanish(); };
15690
15691    apiObj.GetFooterElement = function () {
15692
15693        let retVal = _getModalPrimaryContainerByClassName('appModalFooters'); //must be direct descendent to avoid nested control 
15694         
15695        return retVal;
15696
15697    };
15698     
15699    apiObj.GetTitleElement = function () {
15700        
15701        let retVal = null;
15702
15703        if (apiObj.GetHeaderElement() != null && apiObj.GetHeaderElement().querySelector('span.appModalHeaderTitles') != null)
15704            retVal = apiObj.GetHeaderElement().querySelector('span.appModalHeaderTitles');
15705
15706        return retVal;
15707
15708    };
15709
15710    apiObj.SetTitle = function (title) {
15711
15712        if (apiObj.GetTitleElement() != null)
15713            apiObj.GetTitleElement().innerHTML = title;
15714
15715    };
15716
15717    apiObj.SetContent = function (html) {
15718
15719        if (apiObj.GetMainUIElement() != null)
15720            apiObj.GetMainUIElement().innerHTML = html;
15721
15722    };
15723     
15724    apiObj.ShowFooterBar = function () { if (apiObj.GetFooterElement() != null) apiObj.GetFooterElement().show(); };
15725    apiObj.HideFooterBar = function () { if (apiObj.GetFooterElement() != null) apiObj.GetFooterElement().vanish(); };
15726     
15727    apiObj.GetContinueButtonElement = function () {
15728        
15729        let retVal = null;
15730         
15731        if (apiObj.GetFooterElement() != null && apiObj.GetFooterElement().querySelector('button.appModalFooterContinueButtons') != null)
15732            retVal = apiObj.GetFooterElement().querySelector('button.appModalFooterContinueButtons');
15733
15734        return retVal;
15735
15736    };
15737
15738    apiObj.SetContinueButtonText = function (text) {
15739                
15740        if (apiObj.GetContinueButtonElement() != null && apiObj.GetContinueButtonElement().querySelector('.ui-button-text') != null)
15741            apiObj.GetContinueButtonElement().querySelector('.ui-button-text').innerText = text;        
15742
15743    };
15744
15745    apiObj.SetContinueButtonHtml = function (html) {
15746                
15747        if (apiObj.GetContinueButtonElement() != null)
15748            apiObj.GetContinueButtonElement().innerHTML = html;        
15749
15750    };
15751
15752    apiObj.UpdateContinueButtonFunction = function (jsFunction) {
15753
15754        apiObj.Initialize();
15755
15756        let processButton = apiObj.GetContinueButtonElement();
15757
15758        if (processButton != null && typeof (jsFunction) == 'function') {
15759            processButton.removeAttribute("onclick");
15760            processButton.addEventListener('click', function () { jsFunction(); });
15761        }
15762        
15763    }
15764
15765    apiObj.SetInitialUIState = function () {
15766         
15767        apiObj.HideFooterCloseButton();
15768        apiObj.ShowContinueButton();
15769
15770        //show direct children groups (assumption is that there is a main UI contianer or two)
15771        let uiTopContainers = apiObj.GetMainUIElement().querySelectorAll(':scope > .formFieldRowGroupContainers');
15772        for (let x = 0; x < uiTopContainers.length; x++)
15773            uiTopContainers[x].show();
15774
15775    };
15776
15777    apiObj.SetActionCompleteUIState = function () {
15778         
15779        apiObj.ShowFooterCloseButton();
15780        apiObj.HideContinueButton();
15781
15782        //hide direct children groups (assumption is that there is a main UI contianer or two)
15783        let uiTopContainers = apiObj.GetMainUIElement().querySelectorAll(':scope > .formFieldRowGroupContainers');
15784        for (let x = 0; x < uiTopContainers.length; x++)
15785            uiTopContainers[x].vanish();
15786
15787    };
15788
15789    apiObj.ShowContinueButton = function () { if (apiObj.GetContinueButtonElement() != null) apiObj.GetContinueButtonElement().show(); };
15790    apiObj.HideContinueButton = function () { if (apiObj.GetContinueButtonElement() != null) apiObj.GetContinueButtonElement().vanish(); };
15791
15792    let _getFooterCloseButtonElem = function () { return apiObj.GetFooterElement().querySelector('.appModalFooterCancelButtons'); };
15793
15794    apiObj.ShowFooterCloseButton = function () { if (_getFooterCloseButtonElem() != null) _getFooterCloseButtonElem().show(); };
15795    apiObj.HideFooterCloseButton = function () { if (_getFooterCloseButtonElem() != null) _getFooterCloseButtonElem().vanish(); };
15796     
15797    let _initializeAndShowModal = function (modalElem) {
15798         
15799        if (modalElem != null)
15800            modalElem.show();                     
15801         
15802    };
15803
15804    //core APIs
15805    apiObj.Open = function () {
15806
15807        //console.log('modal api open api called (' + this.GetContainerElement().id + ')');
15808         
15809        apiObj.Initialize(); //always try to initialize on open
15810         
15811        _initializeAndShowModal(this.GetContainerElement());
15812
15813        document.addEventListener('keyup', _handleModalKeyPress);
15814
15815        apiObj.FireModalOpenedEvent();
15816
15817    };
15818     
15819    apiObj.Close = function () {        
15820
15821        apiObj.GetContainerElement().vanish();
15822         
15823        document.removeEventListener('keyup', _handleModalKeyPress);
15824
15825        apiObj.FireModalClosedEvent();
15826
15827    };
15828
15829    let _handleModalKeyPress = function (e) {
15830
15831        console.log('key up pressed for modal ' + apiObj.ModalContainerElementId + ' w key: ' + e.key);
15832        
15833        if (e.key == 'Escape') { apiObj.Close(); }
15834         
15835    };
15836     
15837    apiObj.IsModalOpen = function () { return (apiObj.GetContainerElement() != null && apiObj.GetContainerElement().isVisible()); };
15838
15839    apiObj.ToggleVisibility = function () {
15840
15841        if (apiObj.IsModalOpen())
15842            apiObj.Close();
15843        else
15844            apiObj.Open();
15845
15846    };
15847     
15848    apiObj.IsFullScreen = function () {
15849
15850        let retVal = apiObj.GetContainerElement().hasClassName('fullScreenMode');
15851
15852        return retVal;
15853
15854    };
15855
15856    apiObj.ToggleFullScreen = function () {
15857
15858        let fullScreenIcon = apiObj.GetContainerElement().querySelector('.topRightFullScreenIcons');
15859        
15860        if (apiObj.IsFullScreen()) {
15861             
15862            apiObj.GetContainerElement().removeClassName('fullScreenMode');
15863
15864            if (fullScreenIcon != null) {
15865                fullScreenIcon.removeClassName('fa-window-restore');
15866                fullScreenIcon.addClassName('fa-window-maximize');
15867                fullScreenIcon.setAttribute('title', 'maximize window');
15868            }
15869
15870        }
15871        else {
15872             
15873            apiObj.GetContainerElement().addClassName('fullScreenMode');
15874
15875            if (fullScreenIcon != null) {
15876                fullScreenIcon.removeClassName('fa-window-maximize');
15877                fullScreenIcon.addClassName('fa-window-restore');
15878                fullScreenIcon.setAttribute('title', 'restore window');
15879            }
15880
15881        }
15882
15883    };
15884     
15885    //custom call backs
15886    apiObj.RegisterModalOpenedCallBack = function (callBackFunction) {
15887
15888        CallBackManager.add(apiObj.ModalContainerElementId + '_modalOpenedCallBack', callBackFunction, null);
15889
15890    };
15891
15892    apiObj.FireModalOpenedEvent = function () {
15893
15894        CallBackManager.fire(apiObj.ModalContainerElementId + '_modalOpenedCallBack', {}, null);
15895
15896    };
15897
15898    apiObj.RegisterModalClosedCallBack = function (callBackFunction) {
15899         
15900        CallBackManager.add(apiObj.ModalContainerElementId + '_modalClosedCallBack', callBackFunction, null);
15901
15902    };
15903
15904    apiObj.FireModalClosedEvent = function () {
15905         
15906        CallBackManager.fire(apiObj.ModalContainerElementId + '_modalClosedCallBack', {}, null);
15907
15908    };
15909
15910    apiObj.RegisterInitializedCallBack = function (callBackFunction) {
15911         
15912        CallBackManager.add(apiObj.ModalContainerElementId + '_modalInitializedCallBack', callBackFunction, null);
15913
15914    };
15915
15916    apiObj.FireInitializedEvent = function () {
15917         
15918        CallBackManager.fire(apiObj.ModalContainerElementId + '_modalInitializedCallBack', {}, null);
15919
15920    };
15921     
15922    //initialize UI
15923    apiObj.setCoreConfigError = function (message) { apiObj.GetContainerElement().innerHTML = '<h1 style="color:red;">
15923' + message + '</h1>'; };
15924
15925    let _isInitialized = false;
15926
15927    apiObj.Initialize = function () {
15928
15929        //console.log('is initialized: ' + _isInitialized);
15930        if (!_isInitialized) { 
15931
15932            let contElem = apiObj.GetContainerElement();
15933            if (contElem != null) {                
15934
15935                //console.log('initialize modal ' + contElem.id);
15936
15937                let requiredUIElementsValidated = true;
15938
15939                //add page overlay class
15940                if (!apiObj.GetContainerElement().hasClassName('pageOverlays')) 
15941                    apiObj.GetContainerElement().addClassName('pageOverlays');
15942
15943                ////make sure class if defined on root element
15944                //if (!apiObj.GetContainerElement().hasClassName('ceModalDia_XXX_logs'))
15945                //    apiObj.GetContainerElement().addClassName('ceModalDia_XXX_logs');                
15946                
15947                if(apiObj.GetInnerContainerElement() == null && requiredUIElementsValidated) {
15948                    requiredUIElementsValidated = false;
15949                    apiObj.setCoreConfigError('modal wrapper UI element not defined (add div with class appModals directly inside the root element)');
15950                }
15951
15952                if (apiObj.GetMainUIElement() == null && requiredUIElementsValidated) {
15953                    requiredUIElementsValidated = false;
15954                    apiObj.setCoreConfigError('modal UI element not defined (add div with class appModalUIContainers)');
15955                }
15956             
15957                if (requiredUIElementsValidated) {                    
15958
15959                    //check for click outside (add class and register click close event)
15960                    if (!isBlank(contElem.getAttribute('data-outside-click-close')) && contElem.getAttribute('data-outside-click-close').toBoolFalseIfInvalid()) {
15961                        
15962                        contElem.addClassName('appClickCloseModals');
15963
15964                        contElem.addEventListener('mousedown', function (evt) {
15965
15966                            //console.log('evt: ' + evt.target.id + ', ' + evt.target.className);
15967                            if(evt.target.hasClassName('pageOverlays'))
15968                                apiObj.Close();
15969
15970                        });
15971
15972                    }
15973                    
15974                    //always add title bar if not defined
15975                    //console.log('has header: ' + apiObj.GetHeaderElement());
15976                    if (apiObj.GetHeaderElement() == null) {
15977
15978                        let headerElem = createAnonymousDomElement('div', 'appModalHeaders');                        
15979
15980                        let headerTitleTextElem = createAnonymousDomElement('span', 'appModalHeaderHeaderTitles');
15981
15982                        if (!isBlank(contElem.getAttribute('data-modal-title')))
15983                            headerTitleTextElem.innerHTML = contElem.getAttribute('data-modal-title');
15984                             
15985                        headerElem.appendChild(headerTitleTextElem); //headerTitle
15986
15987                        let headerTopRightIconsContainerElem = createAnonymousDomElement('div', 'topRightIconsContainers');    
15988                         
15989                        //let headerFullScreenIconElem = createAnonymousDomElement('span', 'fa fa-window-maximize topRightFullScreenIcons');
15990                        //
15991                        //headerFullScreenIconElem.setAttribute('title', 'maximize window');
15992                        //
15993                        //headerFullScreenIconElem.addEventListener('click', function () { apiObj.ToggleFullScreen(); });
15994                        //
15995                        //headerTopRightIconsContainerElem.appendChild(headerFullScreenIconElem);
15996
15997                        let headerCloseIconElem = createAnonymousDomElement('span', 'fa fa-close topRightCloseIcons');
15998
15999                        headerCloseIconElem.addEventListener('click', function () { apiObj.Close(); });
16000
16001                        headerTopRightIconsContainerElem.appendChild(headerCloseIconElem);
16002
16003                        if (!isBlank(contElem.getAttribute('data-show-header-icons')) && !contElem.getAttribute('data-show-header-icons').toBoolFalseIfInvalid())
16004                            headerTopRightIconsContainerElem.vanish();
16005
16006                        headerElem.appendChild(headerTopRightIconsContainerElem);                                                 
16007
16008                        apiObj.GetInnerContainerElement().insertFirstElement(headerElem);
16009                     
16010                    }                    
16011
16012                    //console.log('init modal ' + contElem.id + ' footer: ' + apiObj.GetFooterElement());
16013
16014                    //always add footer bar if not defined                  
16015                    if (apiObj.GetFooterElement() == null) {                        
16016
16017                        let footerElem = createAnonymousDomElement('div', 'appModalFooters');
16018
16019                        if (!isBlank(contElem.getAttribute('data-show-footer-close-button')) && contElem.getAttribute('data-show-footer-close-button').toBoolFalseIfInvalid()) {  
16020
16021                            let closeButtonText = 'Close';
16022                            if (!isBlank(contElem.getAttribute('data-close-button-text')))
16023                                closeButtonText = contElem.getAttribute('data-close-button-text');
16024
16025                            let cancelButton = createAnonymousDomElement('button', 'ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only appModalFooterCancelButtons', null, '<span class="ui-button-text appModalFooterCloseButtonLabels">' + closeButtonText + '</span>');
16026                            cancelButton.setAttribute('type', 'button'); //i'm sure both of these are necessary for Jquery
16027                            cancelButton.setAttribute('role', 'button'); //i'm sure both of these are necessary for Jquery
16028                            cancelButton.setAttribute('aria-disabled', 'false');
16029
16030                            cancelButton.addEventListener('click', function () { apiObj.Close(); });
16031
16032                            footerElem.appendChild(cancelButton);
16033
16034                        }   
16035
16036                        let doNotRenderFooterBlock = (!isBlank(contElem.getAttribute('data-show-footer')) && !contElem.getAttribute('data-show-footer').toBoolFalseIfInvalid());
16037                        if (doNotRenderFooterBlock)
16038                            footerElem.vanish();                        
16039
16040                        apiObj.GetInnerContainerElement().appendChild(footerElem);
16041
16042                        //console.log('init modal ' + contElem.id + ' doNotRenderFooterBlock: ' + doNotRenderFooterBlock);
16043
16044                        if (!doNotRenderFooterBlock) { 
16045
16046                            let continueButtonText = 'Continue';
16047                            if (!isBlank(contElem.getAttribute('data-continue-button-text')))
16048                                continueButtonText = contElem.getAttribute('data-continue-button-text');
16049
16050                            let continueFunc = null;
16051                            if (!isBlank(contElem.getAttribute('data-continue-function')))
16052                                continueFunc = eval(contElem.getAttribute('data-continue-function'));
16053
16054                            //console.log('init modal ' + contElem.id + ' with func: ' + contElem.getAttribute('data-continue-function') + ' (' + continueFunc + ')');
16055                            if (continueFunc != null)
16056                                apiObj.AddButton(continueButtonText, continueFunc, null, 'appModalFooterContinueButtons', 'appModalFooterContinueButtonLabels');
16057                            else
16058                                console.log('modal ' + contElem.id + ' continue function invali
16058d: ' + contElem.getAttribute('data-continue-function'));
16059
16060                        }
16061                        else
16062                            console.log('skip continue button initialize for modal ' + contElem.id + ' because footer disabled by config (data-show-footer-close-button)');
16063
16064                    }                                
16065
16066                }                 
16067                  
16068            }
16069            else
16070                console.log('modal initialization failed (no container element found for id ' + apiObj.ModalContainerElementId + ')');
16071
16072            apiObj.FireInitializedEvent();
16073
16074            _isInitialized = true;            
16075
16076        }
16077
16078    };    
16079     
16080    apiObj.AddSecondaryButton = function (text, jsFunction, domId, cssClasses, textClasses, toolTip) {
16081
16082        apiObj.Initialize();
16083
16084        let addCssClasses = 'popUpButtons popUpButtonsSecondary appModalFooterSecondaryButtons';
16085
16086        if (!isBlank(cssClasses))
16087            addCssClasses += ' ' + cssClasses;
16088
16089        apiObj.AddButton(text, jsFunction, domId, addCssClasses, textClasses, toolTip);
16090
16091    }
16092
16093    apiObj.AddLeftSecondaryButton = function (text, jsFunction, domId, cssClasses, textClasses, toolTip) {
16094
16095        apiObj.Initialize();
16096
16097        let addCssClasses = 'popUpButtons popUpButtonsSecondary appModalFooterSecondaryButtons appModalFooterLeftButtons';
16098
16099        if (!isBlank(cssClasses))
16100            addCssClasses += ' ' + cssClasses;
16101
16102        apiObj.AddButton(text, jsFunction, domId, addCssClasses, textClasses, toolTip);
16103
16104    }
16105
16106    apiObj.AddButton = function (text, jsFunction, domId, cssClasses, textClasses, toolTip) {
16107        
16108        if (apiObj.GetFooterElement() != null) {
16109
16110            let buttonCssClass = 'popUpButtons ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only';
16111
16112            if (!isBlank(cssClasses))
16113                buttonCssClass += ' ' + cssClasses;
16114
16115            let buttonLabelCssClasses = 'ui-button-text';
16116
16117            if (!isBlank(textClasses))
16118                buttonLabelCssClasses += ' ' + textClasses;
16119
16120            let processButton = createAnonymousDomElement('button', buttonCssClass, null, '<span class="' + buttonLabelCssClasses + '">
16120' + text + '</span>');
16121            processButton.setAttribute('type', 'button'); //i'm sure both of these are necessary for Jquery
16122            processButton.setAttribute('role', 'button'); //i'm sure both of these are necessary for Jquery
16123            processButton.setAttribute('aria-disabled', 'false');
16124
16125            if (!isBlank(toolTip))
16126                processButton.setAttribute('title', toolTip);
16127
16128            //Update to onclick so that it can be removed if UpdateContinueButton is called
16129            if (typeof (jsFunction) == 'function')
16130                processButton.setAttribute('onclick', jsFunction.name + '();');
16131                //processButton.addEventListener('click', function () { jsFunction(); });
16132
16133            if (!isBlank(domId))
16134                processButton.id = domId;
16135
16136            //console.log('modal add button with text: ' + text);
16137
16138            apiObj.GetFooterElement().appendChild(processButton);
16139             
16140        }
16141
16142    };
16143     
16144    return apiObj;
16145
16146}
16147
16148function getAppUIUserMessagesApi(config) {
16149
16150    const thisApi = {};
16151
16152    thisApi.MainContainerElementDomId = config.MainContainerElementDomId;
16153
16154    const _logger = getConsoleLogApi({ "LogSourceName": 'CE UI Messages Api' });
16155
16156    const _getMainContainerElement = function () {
16157
16158        let retVal = document.getElementById(thisApi.MainContainerElementDomId);
16159
16160        if (retVal == null)
16161            retVal = document.body;
16162
16163        return retVal;
16164
16165    };
16166
16167    const _getSuccessMessageElement = function () { return document.getElementById('successMessage'); };
16168    const _getErrorMessageElement = function () { return document.getElementById('errorMessage'); };
16169    const _getWarningMessageElement = function () { return document.getElementById('warningMessage'); };
16170    const _getInformationMessageElement = function () { return document.getElementById('informationMessage'); };
16171
16172    const _getToastPopUpMessageElement = function () { return document.getElementById('toastPopUpMessage'); };
16173
16174    const _setMessage = function (elem, message, disableAutoScroll) {
16175
16176        if (elem != null) {
16177
16178            elem.innerHTML = message;
16179
16180            if (!isBlank(elem.innerHTML)) {
16181
16182                elem.show();
16183
16184                if (!disableAutoScroll)
16185                    scrollToTopOfWindow();
16186
16187            }
16188            else
16189                elem.vanish();
16190
16191        }
16192
16193    };
16194
16195    thisApi.SetSuccess = function (message, disableAutoScroll) { _setMessage(_getSuccessMessageElement(), message, disableAutoScroll); };
16196
16197    thisApi.ClearSuccess = function () { _setMessage(_getSuccessMessageElement(), ''); };
16198
16199    thisApi.SetError = function (message, disableAutoScroll) { _setMessage(_getErrorMessageElement(), message, disableAutoScroll); };
16200
16201    thisApi.ClearError = function () { _setMessage(_getErrorMessageElement(), ''); };
16202
16203    thisApi.SetWarning = function (message, disableAutoScroll) { _setMessage(_getWarningMessageElement(), message, disableAutoScroll); };
16204
16205    thisApi.ClearWarning = function () { _setMessage(_getWarningMessageElement(), ''); };
16206
16207    thisApi.SetInformation = function (message, disableAutoScroll) { _setMessage(_getInformationMessageElement(), message, disableAutoScroll); };
16208
16209    thisApi.ClearInformation = function () { _setMessage(_getInformationMessageElement(), ''); };
16210
16211    thisApi.ClearMessages = function () {
16212
16213        thisApi.ClearSuccess();
16214        thisApi.ClearError();
16215        thisApi.ClearWarning();
16216        thisApi.ClearInformation();
16217
16218    };
16219
16220    thisApi.ShowToastPopUp = function (message) {
16221
16222        const toastElem = _getToastPopUpMessageElement();
16223
16224        //_logger.Log('show toast called with elem ' + toastElem + ' and message: ' + message);
16225
16226        if (toastElem != null && !isBlank(message)) {
16227
16228            toastElem.textContent = message;
16229            toastElem.addClassName('showToastPopUp');
16230
16231            setTimeout(
16232                function () {
16233                    toastElem.removeClassName('showToastPopUp');
16234                },
16235                1600
16236            );
16237
16238        }
16239
16240    };
16241
16242    thisApi.SetFromPublicAjaxResponse = function (result) {
16243
16244        console.log('set result from ajax: ' + JSON.stringify(result));
16245
16246        if (result != null) {
16247
16248            if (result.Success)
16249                thisApi.SetSuccess('Success!');
16250            else { 
16251                console.log('set error message: ' + result.ErrorMessage);
16252                thisApi.SetError(result.ErrorMessage);            
16253            }
16254
16255        }
16256        else
16257            thisApi.SetError('Unknown error (contact system administrator)');
16258
16259
16260    };
16261
16262    //Initialize
16263    thisApi.Initialize = function () {
16264
16265        _logger.Log('initialize started');
16266
16267        if (isBlank(thisApi.MainContainerElementDomId))
16268            _logger.Log('MainContainerElementDomId not defined (document body will be used for user messages)');
16269
16270        const mainContainerElement = _getMainContainerElement();
16271        if (mainContainerElement != null) {
16272
16273            let erroMessageContainer = document.getElementById('errorMessage');
16274            if (erroMessageContainer == null) {
16275
16276                erroMessageContainer = document.createElement('div');
16277                erroMessageContainer.setAttribute('id', 'errorMessage');
16278                erroMessageContainer.setAttribute('class', 'error');
16279
16280                mainContainerElement.prepend(erroMessageContainer);
16281
16282                erroMessageContainer.vanish();
16283
16284            }
16285
16286            let successMessageContainer = document.getElementById('successMessage');
16287            if (successMessageContainer == null) {
16288
16289                successMessageContainer = document.createElement('div');
16290                successMessageContainer.setAttribute('id', 'successMessage');
16291
16292                mainContainerElement.prepend(successMessageContainer);
16293
16294                successMessageContainer.vanish();
16295
16296            }
16297
16298            let warningMessageContainer = document.getElementById('warningMessage');
16299            if (warningMessageContainer == null) {
16300
16301                warningMessageContainer = document.createElement('div');
16302                warningMessageContainer.setAttribute('id', 'warningMessage');
16303
16304                mainContainerElement.prepend(warningMessageContainer);
16305
16306                warningMessageContainer.vanish();
16307
16308            }
16309
16310            let infoMessageContainer = document.getElementById('informationMessage');
16311            if (infoMessageContainer == null) {
16312
16313                infoMessageContainer = document.createElement('div');
16314                infoMessageContainer.setAttribute('id', 'informationMessage');
16315
16316                mainContainerElement.prepend(infoMessageContainer);
16317
16318                infoMessageContainer.vanish();
16319
16320            }
16321
16322            let toasMessageContainer = document.getElementById('toastPopUpMessage');
16323            if (toasMessageContainer == null) {
16324
16325                toasMessageContainer = document.createElement('div');
16326                toasMessageContainer.setAttribute('id', 'toastPopUpMessage');
16327
16328                mainContainerElement.append(toasMessageContainer);
16329
16330                //toasMessageContainer.vanish();
16331
16332            }
16333
16334        }
16335        else
16336            _logger.Log('unable to initialize User Messages Api becuase no container is available');
16337
16338    };
16339
16340    return thisApi;
16341
16342}
16343
16344function getAppUIConfirmActionPopUpApi() {
16345
16346    const thisApi = {};
16347
16348    const _getConfirmActionOverlayElem = function () { return document.getElementById('confirmActionOverlay'); };
16349    const _getConfirmActionMessagePrimaryTextElem = function () { return document.getElementById('confirmActionText'); };
16350    
16351    const _logger = getConsoleLogApi({ "LogSourceName": 'CE Confirm Action Pop Up Api' });
16352
16353    let _confirmJavascriptFunction = null;
16354    let _cancelJavascriptFunction = null;
16355
16356    thisApi.Open = function (confirmJavascriptFunction, cancelJavascriptFunction) {
16357
16358        let processingOverlayParent = _getConfirmActionOverlayElem();        
16359        if (processingOverlayParent != null) { 
16360
16361            App.UI.Messages.ClearMessages();
16362
16363            processingOverlayParent.show();
16364
16365            _confirmJavascriptFunction = confirmJavascriptFunction;             
16366            _cancelJavascriptFunction = cancelJavascriptFunction;
16367             
16368        }
16369
16370    };
16371
16372    thisApi.Show = function () { return thisApi.Open(); };
16373
16374    thisApi.Close = function () {
16375
16376        let processingOverlayParent = _getConfirmActionOverlayElem();
16377        if (processingOverlayParent != null)
16378            processingOverlayParent.vanish();
16379
16380        _resetMessage();
16381
16382    };
16383
16384    thisApi.Hide = function () { return thisApi.Close(); };
16385
16386    thisApi.SetMessage = function (message)
16387    {
16388
16389        let messElem = _getConfirmActionMessagePrimaryTextElem();
16390        if (messElem != null)
16391            messElem.innerHTML = message;
16392
16393    };
16394
16395    const _resetMessage = function () { thisApi.SetMessage('Are you sure?'); }
16396
16397    const _handleConfirmClick = function () {
16398
16399        thisApi.Close();
16400
16401        if (typeof _confirmJavascriptFunction == 'function')
16402            _confirmJavascriptFunction();
16403
16404    };
16405
16406    const _handleCancelClick = function () {
16407
16408        thisApi.Close();
16409
16410        if (typeof _cancelJavascriptFunction == 'function')
16411            _cancelJavascriptFunction();
16412
16413    };
16414
16415    //Initialize
16416    thisApi.Initialize = function () {
16417
16418        _logger.Log('initialize started');
16419
16420        //build DOM if not injected via legacy
16421        let processingOverlayParent = _getConfirmActionOverlayElem();
16422        if (processingOverlayParent == null) {
16423
16424            processingOverlayParent = document.createElement('div');
16425            processingOverlayParent.setAttribute('id', 'confirmActionOverlay');
16426
16427            //add inner message container
16428            const innerContainer = document.createElement('div');
16429            innerContainer.setAttribute('id', 'confirmActionMessage');
16430
16431            //add text
16432            const textContainer = document.createElement('div');
16433            textContainer.setAttribute('id', 'confirmActionText');            
16434
16435            innerContainer.appendChild(textContainer);
16436
16437            const confirmButtonsContainer = document.createElement('div');
16438            confirmButtonsContainer.id = 'confirmActionModalButtonsContainer';
16439            
16440            const contButtonElem = document.createElement('button');            
16441            contButtonElem.setAttribute('title', 'click to contiue');
16442
16443            contButtonElem.innerHTML = 'Continue';
16444
16445            contButtonElem.addEventListener('click', function () { _handleConfirmClick(); });
16446
16447            confirmButtonsContainer.appendChild(contButtonElem);
16448              
16449            const cancelButtonElem = document.createElement('button');            
16450            cancelButtonElem.setAttribute('title', 'click to cancel');
16451            cancelButtonElem.addClassName('cancel');
16452
16453            cancelButtonElem.innerHTML = 'Cancel';
16454
16455            cancelButtonElem.addEventListener('click', function () { _handleCancelClick(); });
16456
16457            confirmButtonsContainer.appendChild(cancelButtonElem);
16458
16459            innerContainer.appendChild(confirmButtonsContainer);
16460
16461            processingOverlayParent.appendChild(innerContainer);
16462
16463            document.body.appendChild(processingOverlayParent);
16464
16465            processingOverlayParent.vanish();
16466
16467            _resetMessage();
16468
16469        }
16470
16471    };
16472
16473    return thisApi;
16474
16475}
16476
16477function getAppUIProcessingOverlayApi() {
16478
16479    const thisApi = {};
16480
16481    const _getProcessingOverlayElem = function () { return document.getElementById('processingOverlay'); };
16482    const _getProcessingMessagePrimaryTextElem = function () { return document.getElementById('processingText'); };
16483    
16484    const _logger = getConsoleLogApi({ "LogSourceName": 'CE Processing Overlay Api' });
16485
16486    thisApi.Open = function () {
16487
16488        let processingOverlayParent = _getProcessingOverlayElem();        
16489        if (processingOverlayParent != null)
16490            processingOverlayParent.show();
16491
16492    };
16493
16494    thisApi.Show = function () { return thisApi.Open(); };
16495
16496    thisApi.Close = function () {
16497
16498        let processingOverlayParent = _getProcessingOverlayElem();
16499        if (processingOverlayParent != null)
16500            processingOverlayParent.vanish();
16501
16502        _resetMessage();
16503
16504    };
16505
16506    thisApi.Hide = function () { return thisApi.Close(); };
16507
16508    thisApi.SetMessage = function (message)
16509    {
16510
16511        let messElem = _getProcessingMessagePrimaryTextElem();
16512        if (messElem != null)
16513            messElem.innerHTML = message;
16514
16515    };
16516
16517    const _resetMessage = function () { thisApi.SetMessage('Processing...'); }
16518
16519    //Initialize
16520    thisApi.Initialize = function () {
16521
16522        _logger.Log('initialize started');
16523
16524        //build DOM if not injected via legacy
16525        let processingOverlayParent = _getProcessingOverlayElem();
16526        if (processingOverlayParent == null) {
16527
16528            processingOverlayParent = document.createElement('div');
16529            processingOverlayParent.setAttribute('id', 'processingOverlay');
16530
16531            //add inner message container
16532            let innerContainer = document.createElement('div');
16533            innerContainer.setAttribute('id', 'processingMessage');
16534
16535            //add text
16536            let textContainer = document.createElement('div');
16537            textContainer.setAttribute('id', 'processingText');
16538            //textContainer.innerHTML = 'Processing...';
16539
16540            innerContainer.appendChild(textContainer);
16541
16542            processingOverlayParent.appendChild(innerContainer);
16543
16544            document.body.appendChild(processingOverlayParent);
16545
16546            processingOverlayParent.vanish();
16547
16548            _resetMessage();
16549
16550        }
16551
16552    };
16553
16554    return thisApi;
16555
16556}
16557
16558function getCaptchaMfaModalApi() {
16559
16560    const thisApi = {};
16561
16562    let _getModalOverlayElem = function () { return document.getElementById('mfaModalOverlay'); };
16563
16564    let _logger = getConsoleLogApi({ "LogSourceName": 'CE MFA Modal Api' });
16565
16566    let _codeValidatedCallBack = new function () { _logger.Log('code validated call back not implemented'); };
16567
16568    thisApi.Open = function (codeValidatedCallBack) {
16569
16570        thisApi.Initialize();
16571
16572        let modalOverlayParent = _getModalOverlayElem();
16573        if (modalOverlayParent != null) { 
16574
16575            _codeValidatedCallBack = codeValidatedCallBack;
16576
16577            modalOverlayParent.show();
16578
16579            _clearErrorMessage();
16580
16581            thisApi.SetTitle('We couldn\'t verify that you\'re a human.');            
16582
16583            _showRequestCodeView();
16584
16585            thisApi.SelectPhone();            
16586
16587        }
16588
16589    };
16590
16591    thisApi.Close = function () {
16592
16593        let modalOverlayParent = _getModalOverlayElem();
16594        if (modalOverlayParent != null)
16595            modalOverlayParent.vanish();
16596
16597    };
16598
16599    thisApi.SetTitle = function (title) { document.getElementById('mfaModalTopMessage').innerHTML = title; };        
16600    thisApi.SetMessage = function (message) { document.getElementById('mfaResendCodeMessage').innerHTML = message; }; 
16601    thisApi.SetPhoneNumber = function (val) { document.getElementById('mfaResendPhoneNumber').setValue(val); _handleResendPhoneNumberChange(); };
16602    thisApi.SetEmail = function (val) { document.getElementById('mfaResendEmailAddress').setValue(val); _handleResendEmailChange(); };
16603    thisApi.SelectPhone = function () { document.getElementById('mfaResendOptionsPhone').checked = true; _handleMethodRadioChanged(); };
16604    thisApi.SelectEmail = function () { document.getElementById('mfaResendOptionsEmail').checked = true; _handleMethodRadioChanged(); };
16605    thisApi.RequestCode = function () { _handleResendMfaCodeClick(); };
16606    thisApi.SetSendCodeButtonText = function (text) { if(!isBlank(text)) document.getElementById('mfaCodeSubmitButton').setAttribute('value',text);  };
16607
16608    thisApi.GetPhoneNumber = function () { return document.getElementById('mfaResendPhoneNumber').getValue(); };
16609    thisApi.GetEmail = function () { return document.getElementById('mfaResendEmailAddress').getValue(); };
16610
16611    let _setErrorMessage = function (mess) {
16612
16613        document.getElementById('mfaModalTopErrorMessage').vanish();
16614
16615        if (!isBlank(mess)) {
16616            document.getElementById('mfaModalTopErrorMessage').show();
16617            document.getElementById('mfaModalTopErrorMessage').innerHTML = mess;
16618        }
16619
16620    }
16621
16622    let _clearErrorMessage = function () { _setErrorMessage(''); };
16623
16624    let _isCodeValid = function () {
16625
16626        let codeVal = document.getElementById('mfaCode').getValue();
16627        
16628        return (!isBlank(codeVal) && codeVal.length == 4);
16629
16630    };
16631
16632    let _handleEnterCodeChange = function () {
16633         
16634        document.getElementById('mfaCodeSubmitButton').vanish();
16635        if (_isCodeValid())
16636            document.getElementById('mfaCodeSubmitButton').show();
16637
16638    };
16639
16640    let _handleMethodRadioChanged = function () {
16641
16642        document.getElementById('mfaResendPhoneNumber').vanish();
16643        document.getElementById('mfaResendEmailAddress').vanish();
16644
16645        if (document.getElementById('mfaResendOptionsEmail').checked)
16646            document.getElementById('mfaResendEmailAddress').show();
16647        else
16648            document.getElementById('mfaResendPhoneNumber').show();
16649
16650        _handleResendPhoneNumberChange();
16651        _handleResendEmailChange();
16652
16653    };
16654
16655    let _handleEnterCodeClick = function () {
16656
16657        let codeInput = document.getElementById('mfaCode');
16658        codeInput.validationSucceeded();
16659
16660        if (_isCodeValid()) { 
16661
16662            let params = {
16663                "ajaxParams": {
16664                    "UserSuppliedMfaCode": document.getElementById('mfaCode').getValue(), 
16665                }
16666            };
16667
16668            App.PostJsonAsync('/api/PublicWebAppUIAjaxEndpoint/ValidateCaptchaMfaCode',
16669                JSON.stringify(params),
16670                function (result) {
16671
16672                    if (result.Success) {
16673
16674                        thisApi.Close(); //always close on success
16675
16676                        _codeValidatedCallBack();
16677
16678                        //save contact info for future UX
16679                        let ph = App.UI.CaptchaMfaModal.GetPhoneNumber();
16680                        if(!isBlank(ph))
16681                            App.LocalStorage.Save('captcha_mfa_last_phone_number', ph);
16682
16683                        let eml = App.UI.CaptchaMfaModal.GetEmail();
16684                        if(!isBlank(eml))
16685                            App.LocalStorage.Save('captcha_mfa_last_email_address', eml);
16686
16687                        //_fireCodeValidatedEvent();     
16688
16689                    }
16690                    else { 
16691
16692                        _logger.Log('invalid code');                    
16693
16694                        _setErrorMessage(result.ErrorMessage)
16695                         
16696                        codeInput.validationFailed();
16697
16698                    }
16699
16700                },
16701                function (error) {             
16702                    _logger.Log('error: ' + error);                     
16703                });                           
16704
16705        }
16706        else
16707            codeInput.validationFailed();
16708
16709    };    
16710
16711    let _handleResendMfaCodeClick = function () {
16712
16713        _clearErrorMessage();
16714
16715        //_showRequestCodeView();
16716
16717        let modeSelected = document.getElementById('mfaResendOptionsEmail').checked ? 'email' : 'phone';
16718
16719        let phoneInput = document.getElementById('mfaResendPhoneNumber');
16720        let emailInput = document.getElementById('mfaResendEmailAddress');
16721
16722        phoneInput.validationSucceeded();
16723        emailInput.validationSucceeded();
16724
16725        let phoneToUse = '';
16726        let emailToUse = '';
16727
16728        if (modeSelected == 'phone') {
16729
16730            if (!isBlank(phoneInput.getValue()))
16731                phoneToUse = phoneInput.getValue();
16732            else
16733                phoneInput.validationFailed();
16734
16735        }
16736        else {
16737
16738            if (isValidEmailAddress(emailInput.getValue()))
16739                emailToUse = emailInput.getValue();
16740            else
16741                emailInput.validationFailed();
16742
16743        }
16744
16745        if (!isBlank(phoneToUse + emailToUse)) {
16746
16747            let params = {
16748                "ajaxParams": {
16749                    "PhoneNumber": phoneToUse,
16750                    "EmailAddress": emailToUse 
16751                }
16752            };
16753
16754            App.PostJsonAsync('/api/PublicWebAppUIAjaxEndpoint/GenerateCaptchaMfaCode',
16755                JSON.stringify(params),
16756                function (result) {
16757
16758                    if (result.Success) {
16759                        _showEnterCodeView();
16760                        _fireResendCodeEvent(phoneToUse, emailToUse);
16761                    }
16762                    else { 
16763
16764                        _logger.Log('unable to generate captcha mfa code');                    
16765
16766                        _setErrorMessage(result.ErrorMessage)
16767
16768                        if (modeSelected == 'phone')
16769                            phoneInput.validationFailed();
16770                        else
16771                            emailInput.validationFailed();
16772
16773                    }
16774
16775                },
16776                function (error) {             
16777                    _logger.Log('error: ' + error);                     
16778                });
16779
16780        }            
16781
16782    };
16783
16784    let _handleResendPhoneNumberChange = function () {
16785
16786        if (document.getElementById('mfaResendOptionsPhone').checked) { 
16787            
16788            document.getElementById('mfaResendCodeButton').vanish();
16789            if (!isBlank(document.getElementById('mfaResendPhoneNumber').getValue()))
16790                document.getElementById('mfaResendCodeButton').show();
16791
16792        }
16793
16794    };
16795
16796    let _handleResendEmailChange = function () {
16797             
16798        if (document.getElementById('mfaResendOptionsEmail').checked) { 
16799            
16800            document.getElementById('mfaResendCodeButton').vanish();
16801            if (!isBlank(document.getElementById('mfaResendEmailAddress').getValue()))
16802                document.getElementById('mfaResendCodeButton').show();
16803
16804        }
16805
16806    };
16807
16808    thisApi.RegisterInitializedCallBack = function (callBackFunction) {
16809        CallBackManager.add('mfaModalInitializedCallBack', callBackFunction, null);
16810    };
16811
16812    let _fireInitializedEvent = function () {
16813        CallBackManager.fire('mfaModalInitializedCallBack', {}, null);
16814    };
16815
16816    //thisApi.RegisterCodeValidatedCallBack = function (callBackFunction) {
16817    //    CallBackManager.add('mfaModalCodeValidatedCallBack', callBackFunction, null);
16818    //};
16819    //
16820    //let _fireCodeValidatedEvent = function () {
16821    //    CallBackManager.fire('mfaModalCodeValidatedCallBack', {}, null);
16822    //};
16823
16824    thisApi.RegisterResendCodeCallBack = function (callBackFunction) {
16825        CallBackManager.add('mfaModalResendCodeCallBack', callBackFunction, null);
16826    };
16827
16828    let _fireResendCodeEvent = function () { 
16829        CallBackManager.fire('mfaModalResendCodeCallBack', {}, null); 
16830    };
16831
16832    let _showRequestCodeView = function () {
16833
16834        thisApi.SetMessage('We can send you a one-time code to continue.');
16835
16836        document.getElementById('mfaEnterCodeForm').vanish();
16837        document.getElementById('resendCodeForm').show();
16838        document.getElementById('didNotGetCodeLinkContainer').vanish();
16839
16840        if (document.getElementById('mfaResendOptionsEmail').checked)
16841            document.getElementById('mfaResendEmailAddress').focus();
16842        else
16843            document.getElementById('mfaResendPhoneNumber').focus();
16844
16845    };
16846
16847    let _showEnterCodeView = function () {
16848
16849        let modeSelected = document.getElementById('mfaResendOptionsEmail').checked ? 'email' : 'phone';
16850
16851        thisApi.SetMessage('Enter code we sent to your ' + modeSelected + ' to continue.');
16852
16853        document.getElementById('mfaEnterCodeForm').show();        
16854        document.getElementById('resendCodeForm').vanish();
16855        document.getElementById('didNotGetCodeLinkContainer').show();
16856
16857        _handleEnterCodeChange();
16858
16859        document.getElementById('mfaCode').focus();
16860
16861    };
16862
16863    //Initialize
16864    thisApi.Initialize = function () {
16865          
16866        //build DOM if not injected via legacy
16867        let modalOverlayParent = _getModalOverlayElem();
16868        if (modalOverlayParent == null) {
16869
16870            _logger.Log('initialize started');
16871
16872            modalOverlayParent = document.createElement('div');
16873            modalOverlayParent.setAttribute('id', 'mfaModalOverlay');
16874
16875            //add inner message container
16876            let modalBox = document.createElement('div');
16877            modalBox.setAttribute('id', 'mfaModalBox');
16878
16879            //add error message
16880            let topErrorMessage = document.createElement('div');
16881            topErrorMessage.setAttribute('id', 'mfaModalTopErrorMessage');
16882            topErrorMessage.vanish();
16883
16884            modalBox.appendChild(topErrorMessage);
16885
16886            //add message
16887            let messageContainer = document.createElement('div');
16888            messageContainer.setAttribute('id', 'mfaModalTopMessage');
16889            //messageContainer.innerHTML = ''; //dynamic
16890
16891            modalBox.appendChild(messageContainer);
16892
16893            let resendCodeContainer = document.createElement('div');
16894            resendCodeContainer.setAttribute('id', 'mfaResendCodeMessage');
16895            //resendCodeContainer.innerHTML = ''; //dynamic
16896
16897            modalBox.appendChild(resendCodeContainer);
16898
16899            //enter code form
16900            let formContainer = document.createElement('div');
16901            formContainer.setAttribute('id', 'mfaEnterCodeForm');
16902
16903            let codeInput = document.createElement('input');
16904            codeInput.setAttribute('type', 'text');
16905            codeInput.setAttribute('id', 'mfaCode');
16906            codeInput.setAttribute('maxlength', '4');
16907            codeInput.setAttribute('autocomplete', 'one-time-code');            
16908            codeInput.setAttribute('placeholder', 'enter code');
16909
16910            codeInput.addEventListener('input', function () { _handleEnterCodeChange(); });
16911
16912            codeInput.addEventListener('keyup', function (event) { if (event.key == 'Enter') _handleEnterCodeClick(); });
16913
16914            formContainer.appendChild(codeInput);
16915
16916            let submitMfaCodeButton = createClickInputButton('mfaCodeSubmitButton', '', '', 'Complete Transaction', function () { _handleEnterCodeClick(); });
16917
16918            formContainer.appendChild(submitMfaCodeButton);
16919
16920            modalBox.appendChild(formContainer);
16921
16922            //add resend UX
16923            let didNotGetCodeLinkContainer = document.createElement('div');
16924            didNotGetCodeLinkContainer.setAttribute('id', 'didNotGetCodeLinkContainer');
16925
16926            let didNotGetCodeLink = document.createElement('a');
16927            didNotGetCodeLink.setAttribute('id', 'didNotGetCodeLink');
16928            didNotGetCodeLink.setAttribute('href', 'javascript:void(0)');
16929            didNotGetCodeLink.innerHTML = 'Didn\'t receive code?';
16930
16931            didNotGetCodeLink.addEventListener('click', function () { _showRequestCodeView(); });
16932
16933            didNotGetCodeLinkContainer.appendChild(didNotGetCodeLink);
16934
16935            modalBox.appendChild(didNotGetCodeLinkContainer);
16936
16937            //resend form
16938            let resendFormContainer = document.createElement('div');
16939            resendFormContainer.setAttribute('id', 'resendCodeForm');
16940
16941            //radio choice selector
16942            let radioDiv = document.createElement('div');
16943            radioDiv.setAttribute('id', 'resendCodeFormMethodOptionsContainer');
16944
16945            let phoneRadioDiv = document.createElement('div');
16946
16947            let phoneRadio = document.createElement('input');
16948            phoneRadio.setAttribute('type', 'radio');
16949            phoneRadio.setAttribute('name', 'mfaResendOptions');
16950            phoneRadio.setAttribute('id', 'mfaResendOptionsPhone');
16951            phoneRadio.setAttribute('value', 'phone');
16952            phoneRadio.checked = true;
16953
16954            phoneRadio.addEventListener('change', function () { _handleMethodRadioChanged(); });
16955
16956            phoneRadioDiv.appendChild(phoneRadio);
16957
16958            let phoneLabel = document.createElement('label');
16959            phoneLabel.setAttribute('for', 'mfaResendOptionsPhone');
16960            phoneLabel.innerText = 'Send to my phone';
16961
16962            phoneRadioDiv.appendChild(phoneLabel);
16963
16964            radioDiv.appendChild(phoneRadioDiv);
16965
16966            let emailRadioDiv = document.createElement('div');
16967
16968            let emailRadio = document.createElement('input');
16969            emailRadio.setAttribute('type', 'radio');
16970            emailRadio.setAttribute('name', 'mfaResendOptions');
16971            emailRadio.setAttribute('id', 'mfaResendOptionsEmail');
16972            emailRadio.setAttribute('value', 'email');
16973
16974            emailRadio.addEventListener('change', function () { _handleMethodRadioChanged(); });
16975
16976            emailRadioDiv.appendChild(emailRadio);
16977
16978            let emailLabel = document.createElement('label');
16979            emailLabel.setAttribute('for', 'mfaResendOptionsEmail');
16980            emailLabel.innerText = 'Send to my email';
16981
16982            emailRadioDiv.appendChild(emailLabel);
16983
16984            radioDiv.appendChild(emailRadioDiv);
16985
16986            resendFormContainer.appendChild(radioDiv);
16987
16988            //add value inputs
16989            let mfaPhoneInput = document.createElement('input');
16990            mfaPhoneInput.setAttribute('type', 'text');
16991            mfaPhoneInput.setAttribute('id', 'mfaResendPhoneNumber');
16992            mfaPhoneInput.setAttribute('maxlength', '15');
16993            mfaPhoneInput.setAttribute('placeholder', 'Enter phone number');
16994
16995            mfaPhoneInput.addEventListener('input', function () { _handleResendPhoneNumberChange(); });
16996
16997            resendFormContainer.appendChild(mfaPhoneInput);
16998
16999            let mfaEmailInput = document.createElement('input');
17000            mfaEmailInput.setAttribute('type', 'text');
17001            mfaEmailInput.setAttribute('id', 'mfaResendEmailAddress');
17002            mfaEmailInput.setAttribute('maxlength', '500');
17003            mfaEmailInput.setAttribute('placeholder', 'Enter email');
17004
17005            mfaEmailInput.addEventListener('input', function () { _handleResendEmailChange(); });
17006
17007            resendFormContainer.appendChild(mfaEmailInput);
17008
17009            let submitMfaResendCodeButton = createClickInputButton('mfaResendCodeButton', '', '', 'Get Code', function () { _handleResendMfaCodeClick(); });
17010
17011            resendFormContainer.appendChild(submitMfaResendCodeButton);
17012
17013            modalBox.appendChild(resendFormContainer);
17014
17015            modalOverlayParent.appendChild(modalBox);
17016
17017            document.body.appendChild(modalOverlayParent);
17018
17019            modalOverlayParent.vanish();
17020
17021            _fireInitializedEvent();
17022
17023        }
17024        else
17025            _logger.Log('skipping initialize (already initailized');        
17026
17027    };    
17028
17029    return thisApi;
17030
17031}
17032
17033
17034/*App Core Footer*/
17035
17036//DEFINE EVERGREEN VARIALES
17037const CE = getCeApi();
17038
17039window.CE = CE; //for external script access (e.g., googlepay onload call back)
17040
17041const gl_ce_pageHostName = getPageHostName();

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.