/*
 * olgDonor2.js
 */

'use strict';
/*
 * @controller profile
 * online giving form front-end for users
 *
 */
console.log('In olgDonor2.js');

var olgDonor2 = {
    // These help track how many gifts have been added to the page
    giftsShown: 0,
    MAX_GIFTS_PER_PAGE: 5,
    giftsOpen: [null, true, false, false, false, false],
    newGiftsOpen: [],

    // Messages shown on the "Your Donation" or "Your Membership" fragments when no gift or membership has been entered
    defaultSummaryMsg: 'Please fill out your gift information',
    defaultMembershipMsg: 'Please fill out the membership information',

    cyberFormLoads: 0,
    // CYBER_TIMEOUT: 10000,
    CYBER_TIMEOUT: 30000,
    // CYBER_TIMEOUT: 2000,
    dCyber: null, // This will be filled up with our $.Deferred object used to handle cybersource problems

    // Arrays used for data signing
    signedFields: [],
    merchantFields: [],
    unsignedFields: [],

    // Object to hold the user gift amount entry template
    $userGiftBlock: null,

    donorSpinner: {},

    /*
     * @action init
     *
     */
    init: function () {
        'use strict';
        // Initialize signed, unsigned, and merchant field variables
        this.initVars();

        // Set up event watchers
        this.initUserEvents();

        // set up country/address dynamic validation
        this.initCountries();

        // Initialize UI for Gift Amount buttons, funds, schools and additional gifts
        if (isGiftForm) {
        	olgGift2.init();
        	olgGift2.setGiftCommentVisibility();
        }
        
        // Copy address info to the "payment info is different" fields
        $('#contactCountry').change();

        // Call handler when the hidden IFrame loads.  This could be called when the response comes back from
        // Cybersource, or after that, when it comes back from our server
        var myFrame = $('#cyberFrame');
        myFrame.on('load', null, this, this.cyberFrameLoad);
        myFrame.on("error", function() {
            alert('Error: ');
        });

        // Set up the user timeout notification System
        olgDonor2.initializeUserTimeout();

        // Set up the Confirm Duplicate Gift Dialog
        $('#recent-modal button').on('click', function (e) {
            var $target = $(e.target); // Clicked button element
            $(this).closest('.modal').on('hidden.bs.modal', function () {
                olgDonor2.confirmDuplicateGift(e, $target);
            });
        });

        $('.js-custom-radio').on('change', function(ev){
            $('input[name="ca_' + ev.target.name.substr(3) + '"]').val($(ev.target).next('label').html());
        });
        // Set up Date Picker
	    $("#recurringStartDate").datepicker({startDate: new Date()});
	    $("#recurringStartDate").datepicker("setDate", new Date());
	    // Insert date from date picker into the cybersource payment hidden field.
	    var self = this;
	    $("#recurringStartDate").datepicker().on("changeDate", function(ev) {
	    	$('#recurring_start_date').val(self.cyberFormatDate(new Date($("#recurringStartDate").val())) );
	    });
	    $("#recurringStartDate").datepicker().on("clearDate", function(ev) {
	    	$('#recurring_start_date').val('');
	    });
        // Set up contact info fields to copy from the ship-to to the bill-to lines for Credit card auto-fill
        $('[data-copytarget]').on('change', function(){
            $('#' + $(this).data('copytarget')).val($(this).val());
        });

        $('[data-transform="uppercase"]').on('change', function(){
        	if ($(this).attr('data-transform') == 'uppercase') {
        		$(this).val($(this).val().toUpperCase());
        	}
        });

        $('select.init-select2').each(function(idx,el) {
        	$(el).select2();
        });

        // run the summary code at init, since a default value will be selected
        this.updateSummary();
    }, // end of init function

    // Functions and events for handling the Cybersource Payment process ======
    pay: function(csrf_token, recaptcha_token) {
        'use strict';
        this.getSignature(csrf_token, recaptcha_token);
    },

    destroySchoolAndFundSelect: function($schoolElement, $fundElement){
        $schoolElement.select2('destroy');
        $fundElement.select2('destroy');
        $schoolElement.off('select2:select');
        $fundElement.off('select2:select');
    },

    fundFormat: function(fund){
        return fund.displayName;
    },


    schoolFormat: function(school){
        return school.description;
    },

    initPaymentProcess: function() {
        this.cyberFormLoads = 0;
        $('#error-msgs').html('');

        // Clear out any fields that are on collapsed (and thus, invisible) forms
        // (Except billing address.  That's handled differently)
        $('#giftJointContent, #giftHonorContent, #giftPledgeContent, #pmtEmplInfo, #membSecondInfo, #membGiftInfo').find(":input").not(":visible").val('');
        $('#giftDetails').not(":visible").html('');
    },

    getSignature: function(csrf_token, recaptcha_token) {
        'use strict';

        // First, trim any spaces around the field values.  Cybersource doesn't like trailing spaces
        $(':input').val(function(idx, myVal) {return $.trim(myVal);});

        // Fill the transactional fields
        var $uuid = $('#transaction_uuid');
        if (!$uuid.val()) { // If it hasn't been filled in, create a new one
            $uuid.val(this.formGroomer.createTransaction_uuid());
        }

        $('#signed_date_time').val(this.formGroomer.getSigned_date_time());

        this.formGroomer.fillMerchantDefinedFields(this);

        $('#signed_field_names').val(this.signedFields.join());
        $('#unsigned_field_names').val(this.unsignedFields.join());

        var signingData = this.formGroomer.createDataToSign(this);

        var headers = {};
        if (csrf_token) {
            headers['X-CSRF-TOKEN'] = csrf_token;
        }
        
        var formId = $('#form_id').val();
        if (formId == "") {
        	formId = 0;
        }

        // @todo move REST ENDPOINT TO CONFIG

        olgDonor2.signedData = null;
        // Remove earlier form which might appear if user presses back button and gets a cached version
        $('#paymentform').remove();
        var url = '/give/' + $('#form_id').val() + '/payment';
        var $form = $('<form/>', { action: url, method: 'POST', id: 'paymentform', name: 'paymentform' })
            .append(
                $('<input>', {type: 'hidden', id: '_csrf', name: '_csrf', value: csrf_token}),
                $('<input>', {type: 'hidden', id: 'formFields', name: 'formFields', value: JSON.stringify(signingData)}),
                $('<input>', {type: 'hidden', id: 'recaptchaFormResponse', name: 'recaptchaFormResponse', value: recaptcha_token}),
                $('<input>', {type: 'submit', id: 'submit', name: 'submit'})
            );

        // There's something odd about the membership form that keeps it from submitting using the regular
        // $form.submit() syntax.  It keeps submitting the main form (#mainForm) instead of the form we put together
        // via javascript.
        // Adding a button to the form and clicking it via Javascript seems to solve the issue
        // The other thing that was needed was to add
        //    onsubmit="return false"
        // To the main form
        $form.appendTo('body');
        $('#paymentform input[type="submit"]').click();
    },

    confirmDuplicateGift: function(e, $btn) {
        var data = olgDonor2.signedData;
        if (data && ($($btn[0]).attr('id') == 'recent.confirmationButton')) {
            $('#signature').val(data['responseData']);
            $('#reference_number').val(data['reference_number']);
            olgDonor2.cyberPost();
        }
    },

    cyberPost: function() {
        // Create the temporary form and submit it
        // #payment_confirmation is a DIV inside of #payment_form
        $('#payment_confirmation').html(this.createCyberForm(this.signedFields, this.unsignedFields));

        this.dCyber = $.Deferred().
        done(function(context, result, merchantData) {
            console.debug('the background transactions are finished... redirecting to confirmation');

            // clear out cyberFrame to prevent duplicate submissions if user navigates 'back' to forms
            var cyberFrame = $('#cyberFrame');
            cyberFrame.off();
            cyberFrame.attr('src', '');

            top.location.assign('/give/forms/confirmed/' + result.transactionId);
        }).
        fail(function(context, result, merchantData) {

            // If the result is bad, (and if we're here, it must be) we need a new transaction UUID before
            // resubmitting. Clearing it out on the form will cause a new one to be genereated
            $('#transaction_uuid').val('');

            // ToDo: These are just for debugging, and should be removed
            console.log('Result');
            console.log(result);
            console.log('Merchant Data');
            console.log(merchantData);
        }).progress(function(ev) {
            // Note: ev.data is the olgDonor2 context
            console.log('Cyber Phase: ' + ev.data.cyberFormLoads + ' complete');
            console.log(ev);
        });

        // ToDo: We need to put this into a timer in case Cybersource is unresponsive or
        // ToDo: Also need to figure out how to handle invalid responses (like 403's, etc.) probably will need a timer
        $("#payment_form").submit();


    },

    createCyberForm: function(signedFields, unsignedFields) {
        'use strict';
        var myForm = "";
        for (var i = 0; i < signedFields.length; i++) {
            myForm += '<input type="hidden" id="tmp_' + signedFields[i] + '"  name="' + signedFields[i] + '" value="' + $("[name='" + signedFields[i] + "']").val() + '" />\n';
        }
        for (i = 0; i < unsignedFields.length; i++) {
            myForm += '<input type="hidden" id="tmp_' + unsignedFields[i] + '" name="' + unsignedFields[i] + '" value="' + $("[name='" + unsignedFields[i] + "']").val() + '" />\n';
        }
        myForm += '<input type="hidden" id="tmp_signature" name="signature" value="' + $('#signature').val() + '" />\n';

        return myForm;
    },

    // What to do when our hidden frame reloads.  Note that this gets called twice.  The first time happens when the
    // response comes back from Cybersource.  We can't really read the stuff via Javascript because it's a cross-origin
    // request.  At that point, it goes to the catch block with e.code = 18
    // The second time is after the form is submitted to our server and another response comes back from there.  That
    // one, we can read.
    cyberFrameLoad: function(ev) {
        var result = {};

        // Note: ev.data is a pointer to the olgDonor2 context (i.e. what would normally be 'this' outside of the function)
        try {
            // firefox or others may try to fire the cyberFrameLoad event long before even submitting the
            // form or before the deferred is even defined. here we just check to be sure we are here
            // when we want to be ...
            if (ev.data.dCyber === null) {
                console.log('load event fired without deferred...');
                return true;
            }

            ev.data.cyberFormLoads++;
            ev.data.dCyber.notify(ev);

            // If we're loading the page from Cybersource, we'll jump to the catch clause when the next line runs
            var content = $(ev.target).contents().find("body").text(),
                contentJson = JSON.parse(content),
                merchantInfo = {};

            _.each(contentJson, function(value, key, list){
                if (value.indexOf('req_merchant_defined_data') !== -1) {
                    merchantInfo[key] = value;
                } else {
                    result[key] = value;
                }
            });

            if (result.transactionId) {
                ev.data.dCyber.resolve(ev.data, result, null);
            } else {

                // If there's an error from Cybersource, clear out the MRN so we'll get a new one after the user fixes the error
                $('#reference_number').val('');

                console.log('about to reject 001');
                // console.log(ev.data);
                // console.log(result);
                // console.log(merchantInfo);

                ev.data.dCyber.reject(ev.data, result, merchantInfo);

            }

        } catch (e) {
            // code 18 is a Security Error:
            // Blocked a frame with origin "http://<some URL>" from accessing a cross-origin frame.
            // This is expected when we try to read the form that comes back from CyberSource.  We can ignore it
            // Any other code, we'll put the error in the console.

            console.warn('try/catch e:', e);

            if (e.code !== 18) {
                console.log(e);
            }
            if (olgDonor2.cyberFormLoads >= 2) {

                console.log('about to reject 002');
                console.log(ev.data);

                olgDonor2.dCyber.reject(ev.data, result, null);
            }
        }
    },
    updateSummary: function() {
        // Most of this function has moved to olgGift2.
        if (isGiftForm) {
        	// When total amount changes validate the last gift
        	// to update total amount error message.
        	var lastGift = olgGift2.getLastGift();
        	lastGift.setCustomValidity(olgGift2.validateGiftAmt(lastGift));
        	lastGift.checkValidity();
        	olgGift2.updateGiftSummary();
            // update the hidden frequency fields
            this.changeFrequency(false); // passing 'false' prevents changeFrequency() from calling back to this
        } else {
          	this.updateMembLevelSummary();
        }
        // copy our temp amount to the actual amount field, for updating
        this.updateActualAmount();

    },


    // Container object that includes functions for massaging form data into something we can send to Cybersource
    formGroomer: {
        getSigned_date_time: function() {
            'use strict';
            return (new Date()).toISOString().substr(0, 19) + 'Z'; // Format: '2015-02-18T23:26:49Z'
        },

        createTransaction_uuid: function() {
            'use strict';
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
                var r = (Math.random() * 16) | 0;
                var v = (c === 'x') ? r : r & 0x3 | 0x8;
                return v.toString(16);
            });
        },

        fillMerchantDefinedFields: function(context) {
            'use strict';
            // context function:
            // 1. Creates a JSON object out of all the fields that CyberSource doesn't care about (found in the
            //      merchantFields array)
            // 2. Stringifies that object
            // 3. Splits the string into 100-character bits
            // 4. Adds form fields merchant_defined_data1, merchant_defined_data2, ... merchant_defined_dataN and parcel out
            //      the strings into them.
            // 5. Adds these fields to the data to sign.

            // Take the gift info fields (amount, school, and fund, and copy them to sequentially numbered hidden fields)
            var counter = 0;
            var ctrlsHtml = '';
            var $ctrl = null;
            for (var i = 0; i <= $('#maxGiftNumber').val(); i++) {
                $ctrl = $('input[name="donation-amt-' + i + '"]');
                if ($ctrl && $ctrl.length && $ctrl.val()) {
                    counter++;
                    ctrlsHtml += '<input type="hidden" name="donationAmt_' + counter + '" value="' + $ctrl.val() + '" />\n'

                    $ctrl = $('select[name="school_' + i + '"]');
                    if ($ctrl && $ctrl.length && $ctrl.val()) {
                        ctrlsHtml += '<input type="hidden" name="school_' + counter + '" value="' + $ctrl.val() + '" />\n'
                    }

                    $ctrl = $('select[name="fund_' + i + '"]');
                    if ($ctrl && $ctrl.length && $ctrl.val()) {
                        ctrlsHtml += '<input type="hidden" name="fund_' + counter + '" value="' + $ctrl.val() + '" />\n'
                    }
                }
            }

            $('#gift-summary').html(ctrlsHtml);

            // Collect the non-cybersource fields into a JavaScript object
            var NO_SELECTED_VALUE = '';
            var result = {};
            var fieldSelector = "";
            var radioButtons = {};
            for (var i = 0; i < context.merchantFields.length; i++) {
                fieldSelector = "[name=" + context.merchantFields[i] + "]";
                // console.log('running fieldselector', fieldSelector, $(fieldSelector).val())
                if ($(fieldSelector).length && $(fieldSelector).val() && $(fieldSelector).val() !== 'false' && $(fieldSelector).val().trim().length) { // Check to make sure the field exists on the current page
                    if ($(fieldSelector).is(':checkbox')) {
                        if ($(fieldSelector).prop('checked')) {
                            result[context.merchantFields[i]] = $(fieldSelector).val();
                        } else {
                            result[context.merchantFields[i]] = NO_SELECTED_VALUE;
                        }
                    } else if ($(fieldSelector).is(':radio')) {
                        result[context.merchantFields[i]] =
                            _.reduce($(fieldSelector), function(result, n, key) {
                                if ($(n).prop('checked')) {
                                    result = $(n).val();
                                }
                                return result
                            }, NO_SELECTED_VALUE);
                    } else {
                        result[context.merchantFields[i]] = $(fieldSelector).val();
                    }
                }
            }

            // Turn it into a string and parcel it out into an array where each element is 100 chars long
            var strResult = encodeURIComponent(JSON.stringify(result));
            $('#all-merch-data').html('\n' + strResult + '\n'); // Throw it into a hidden div to make debugging easier
            var merchData = strResult.match(/.{1,100}/g);

            // Clean up old data from previous attempts, as the data may have been changed
            // Remove HTML input elements
            $('.merchant_defined_data').remove();
            // Remove them from the signed fields array
            for (i = context.signedFields.length - 1; i >= 0; i--) {
                if (context.signedFields[i].indexOf('merchant_defined_data') > -1) {
                    context.signedFields.splice(i, 1);
                }
            }

            // Place the data into the merchant defined fields
            var $newField = null;
            for (i = 0; i < merchData.length; i++) {
                $newField = $('<input />', {
                    type: 'hidden',
                    class: 'merchant_defined_data',
                    id: "merchant_defined_data" + (i + 1),
                    name: "merchant_defined_data" + (i + 1),
                    value: merchData[i]
                });
                $newField.appendTo('#cyber-hidden-fields');

                // Add them to the signed fields array
                context.signedFields.push("merchant_defined_data" + (i + 1));
            }
        },

        // This function needs to return a JavaScript object of the form:
        // { "formFields": ["fieldName1", "fieldName2", "FieldName3", "etc..."],
        //   "formFieldsToDetails":{"fieldName1":"value1","fieldName2":"value2", "etc":"etc..."}
        // }
        createDataToSign: function(context) {
            'use strict';
            // The object should contain all the fields that need to be signed
            // we will then populate the final object with both signedFields and the data that resides in it

            var returnData = {
                "formFields": [],
                "formFieldsToDetails": {}
            };

            for (var i = 0; i < context.signedFields.length; i++) {
                var fieldName = context.signedFields[i];
                returnData.formFields.push(fieldName);
                returnData.formFieldsToDetails[fieldName] = $("[name=" + fieldName + "]").val();
            }

            return returnData;
        }
    },

    updateActualAmount: function(){
        var amt_temp = $('#amount_temp').val();

        if (amt_temp === '' || typeof amt_temp === 'undefined' || amt_temp === null) {
            $('#amount_field').val('0');
        } else {
            $('#amount_field').val( amt_temp.toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2 }) );
        }
    },

    // Converts a date object to a yyyyMMdd string.   
    cyberFormatDate: function(date){
    	var year = date.getFullYear(),
    		month = ('0' + (date.getMonth() + 1)).slice(-2),
    		dayOfTheMonth = ('0' + date.getDate()).slice(-2);
        return year + month + dayOfTheMonth;
    },
    
    // this method handles changing frequence for Gift forms.
    //  - populates hidden cybersource fields as the user selects
    //  - this also takes care of shifting 'amount_temp' to 'amount_field' on membership forms, as it is always called when updating summary
    changeFrequency: function(ev) {
        var $giftFreq = $('#giftFreq');
        var $amount_temp = $('#amount_temp');
        var $amount_actual = $('#amount_field');

        if ($giftFreq.length < 1) {
            // not a gift form, just update 'actual' amount field
            console.warn('not a gift form! freq changes');
            $amount_actual.val( $amount_temp.val() );
            return false;
        }

        var $cyberFreq = $('#recurring_frequency'),
        	$cyberRecurringStartDate = $('#recurring_start_date'),
            $cyberRecurringStartDateFix = $('#recurringStartDateFix'),
            $giftFreqPeriod = $('#giftFreqPeriod'),
            $recurringStartDate = $('#recurringStartDate'),
            giftFreqVal = $giftFreqPeriod.val(),
            $cyberRecurAmt = $('#recurring_amount'),
            isRecurring = $giftFreq.val() !== 'oneTime',
            $tx_type = $('#transaction_type');

        if (!isRecurring || typeof giftFreqVal === 'undefined' || giftFreqVal === '') {
            // zero fields back out if user de-selects recurring option
            $cyberRecurAmt.val('');
            $cyberFreq.val('');
            $cyberRecurringStartDate.val('');
            $tx_type.val('sale');
            this.updateActualAmount();
        } else {
            //update recurring option with primary gift amount
            $cyberRecurAmt.val( $amount_temp.val() );
            $cyberFreq.val( $giftFreqPeriod.val() );
            if ($cyberRecurringStartDateFix.val().length === 10) {
                // we're correcting errors in the fix-it form
                $recurringStartDate.val($cyberRecurringStartDateFix.val());
                $cyberRecurringStartDateFix.val('');
            }
            $cyberRecurringStartDate.val(this.cyberFormatDate(new Date($recurringStartDate.val())) );
            $amount_actual.val('0');
            $tx_type.val('create_payment_token');
        }

        // hacky way to avoid circular function calls
        //  - if this method is called by updateSummary(), 'ev' will be boolean, so do not call back to updateSummary
        //  - if 'ev' is an event object, this was a change, and should call updatesummary
        if (typeof ev !== 'undefined' && typeof ev !== 'boolean') {
            this.updateSummary();
            var $target = $(ev.target);
            var sliderID = $target.data().target;
            var $sliderDiv = $(sliderID);
            var $sibs = $(sliderID).siblings();
            // Hide other frequency
            $sibs.collapse('hide');
            $target.parent().siblings().find('input').attr('aria-expanded', 'false');
            // Open selected frequency
            $sliderDiv.collapse('show');
            $target.attr('aria-expanded', 'false');
        }
    },


    // Initialize user interaction events =====================================
    initUserEvents: function() {
        // Gift Form =====

        // initialize popovers - any 'hidden' tooltips will come back blank, and will be removed during this init
        $("[data-toggle=popover]").each(function(index, element){
            var $element = $(element),
                content = $element.data('content');

            if ((!content) || (content.length === 0) || (content.trim().length == 0)) {
                $element.remove();
            } else {
                $element.popover({trigger: 'hover focus', placement: 'top', html: true});
            }
        });


        // Expander Check Boxes
        $('.js-option-checkbox').change(function() {
            $(this).val($(this).is(':checked'));
        });


        $('body')
            .on('change', '.js-freq', $.proxy(this.changeFrequency, this))
            .on('click', '.js-freq-button', $.proxy(this.changeFrequency, this))

        // prevent clicks on tooltips
            .on('click', "[data-toggle=popover]", function(ev){
                ev.preventDefault();
            })
            .on('change', '#contactCountry', $.proxy(function(ev){
                // NOTE: We need to set the billing country based on the *description* of the contact country
                //       because multiple countries may have the same value.  For example, England and Wales
                //       both have a country code of GB

                var newCountryCode = $(ev.target).val();
                var newCountryDescription = $(ev.target).find("option:selected").html();

                this.setCountryFields(newCountryCode, 'contact');
            }, this))

            // save membership level name in merchant-data
            .on('change', '#membLevel', $.proxy(function(ev){
                var $selected = $(ev.target).find('option:selected');

                $('#membLevelName').val($selected.data('levelName'));
                $('#fund_1').val($selected.data('levelFundCode'));
                $('#membGiftPremiumAmount').val($selected.data('levelPremiumValue'));
                $('#membGiftPremiumCode').val($selected.data('levelPremiumCode'));
            }, this));

        // Recurring Frequency handler
        $('.your-gift-freq-selector')
            .on('focusin', 'input[type="radio"]', function(ev){
                // give the button a focused appearance when radio button is focused
                if ($(ev.target).prop("checked")) {
                    //$(ev.target).parent('.btn').addClass('focused');
                    //olgDonor2.showDescription(ev.target);
                }
            })
            .on('focusout', 'input[type="radio"]', function(ev){
                // clear the focus look when focus changes
                //$(ev.target).parent('.btn').removeClass('focused');
            })
            .on('change', '[type="radio"]', $.proxy(function(ev){
                var $target = $(ev.target);
                // var $sibs = $target.closest('.js-btn-wrapper').siblings();
                //
                // $sibs.find('.active').removeClass('active');
                // $target.parent('.btn').addClass('focused active');
                // this.showDescription(ev.target);

                // update the hidden field holding the true value
                $('#giftFreq').val($target.val());

                this.changeFrequency(ev);
            }, this));

        $('#joint').change( function(){
        	var isShown = $('#joint').prop('checked')
            $('#jointIndicator').val(isShown ? 'opened':'closed');
            $('.js-spouse-required').prop({'required': isShown});
        });

        $('#honor').change( function(){
        	var isShown = $('#honor').prop('checked')
            $('#honorIndicator').val(isShown ? 'opened':'closed');
            $('.js-honor-required').prop({'required': isShown});
        });

        $('#pledge').change( function(){
        	var isShown = $('#pledge').prop('checked')
            $('#pledgeIndicator').val(isShown ? 'opened':'closed');
        	$('#pledgeDetails').attr('aria-expanded', isShown ? 'true' : 'false');
            $('#pledgeDetails').prop({'required': isShown});
        });
        $('#pmtEmplMatch').change( function(){
        	var isShown = $('#pmtEmplMatch').prop('checked');
            $('#pmtEmplMatchIndicator').val(isShown ? 'opened':'closed');
        	$('#pmtEmplName').attr('aria-expanded', isShown ? 'true' : 'false');
            $('#pmtEmplName').prop({'required': isShown});

        });

        // Complete Your Gift/Membership button
        $('.btn-olg-complete').click(function(el) {
            var csrf_token = null;
            var $csfr_element = $(el.target).closest('form').find('input[name="_csrf"]');
            if ($csfr_element) {
                csrf_token = $csfr_element.val();
            }
            if (typeof grecaptcha != 'undefined') {
            	grecaptcha.enterprise.ready(function() {
            	    grecaptcha.enterprise.execute(captchaPublicKey,  {action: 'submit'}).then(function(recaptcha_token) {
	                    if (!olgDonor2.checkForFormErrors()) {
	                        olgDonor2.initPaymentProcess();
	                        olgDonor2.pay(csrf_token, recaptcha_token);
	                    }
	                });
	              });
            } else {
                if (!olgDonor2.checkForFormErrors()) {
	                olgDonor2.initPaymentProcess();
	                olgDonor2.pay(csrf_token, "");
                }
            }

        });
        
        // Things that can be clicked to navigate (like login buttons)
        // Things that can be clicked to navigate (like login buttons)
        $('.js-click-nav').click(function() {
            var href = $(this).data('href');
            //top.location.href = href;
            //window.location.replace(href);
            if (href) {
                setTimeout(function() {
                    top.location.href = href;
                }, 0);
            }
        });

        // floating summary pane changes width when it is pulled out of the DOM to float at the top.
        $('.affixed').on('affix.bs.affix', function() {
            $(this).width($(this).width());
        });


        // Membership Form =====
        $('#membLevel').change( $.proxy(this.changeMembLevel, this) );
        this.changeMembLevel();

        $('#membGift').change( function(){
        	var isShown = $('#membGift').prop('checked')
            $('#membGiftIndicator').val($('#membGift').is(':checked') ? 'opened':'closed');
            $('.js-membGift-required').prop({'required': isShown});
        });

        $('[name="membType"]').on('focusin', function(ev){
            // give the button a focused appearance when radio button is focused
            if (!$(ev.target).prop("checked")) {
                $(ev.target).prop("checked", true)
            }
        });

        $('#membSecond').change( function(){
        	var isShown = $('#membSecond').prop('checked')
            $('#membSecondIndicator').val($('#membSecond').is(':checked') ? 'opened':'closed');
            $('.js-secondary-required').prop({'required': isShown});
        });

        // set up city/state/zip field swapping for country
        $('#membGiftCountry').change($.proxy(function(ev){
            this.setCountryFields($(ev.target).val(), 'membGift');
        }, this));

        $('#membAddlGiftInput').change($.proxy(function() {
            var addlGift = olgGift2.getAdditionalGiftOnMembershipForm(); 
            // Convert Input with a single decimal place to have 2 decimal places
            var isSingleDecimal = (addlGift != Math.floor(addlGift) && addlGift * 10 == Math.floor(addlGift * 10));
            var totalAmount = olgGift2.getTotalAmountOnMembershipForm();
            if (totalAmount == Math.floor(totalAmount)) {
                $('#amount_temp').val(totalAmount);
            } else {
                $('#amount_temp').val(totalAmount.toFixed(2));
            }

            if (addlGift > 0) {
                $('#addlGiftInfo').html('<div class="sum-box__amount" id="amount">$' + addlGift.toLocaleString("en-US") + (isSingleDecimal ? + '0' : '') + '</div><div class="sum-box__msg">Additional Gift</div>');
            } else {
                $('#addlGiftInfo').html('');
            }

            $('#membAddlGift').val(addlGift);
            $('#membAddlGiftInput').val(function () {
                return addlGift == 0 ? "" : addlGift.toLocaleString("en-US") + (isSingleDecimal ? + '0' : ''); 
            }); 
            
            this.updateActualAmount();
        }, this));
    },

    changeMembLevel: function(){
        var $select = $('#membLevel');
        if ($select.length === 0) {return false; }

        var $selected = $select.find(':selected')
        var $secondMembLevel = $('#membLevel option[data-opt-id="' + $selected.data('opt-second-member') + '"]');
        if ($selected && $selected.data('opt-allow-second') && $('#membSecond').length) {
            $('#membSecond').removeAttr("disabled");
        } else {
            if ($('#membSecond').prop('checked')) {
                $('#membSecond').click();
            }
            $('#membSecond').attr("disabled", true);
        }


        var targetData = $select.find(':selected').data('target'),
            curTarget = (typeof targetData === 'undefined' || targetData === '' || targetData === null) ? 'membLevelNone' : targetData;

        $('#membLevelInfo').find('.tab-pane.fade').removeClass('active').removeClass('show');
        $('#' + curTarget).addClass('active').addClass('show');

        this.updateMembLevelSummary();
        this.updateActualAmount();
    },


    updateMembLevelSummary: function(){
        var $select = $('#membLevel');

        if ($select.length === 0) {return false; }

        // Show the appropriate descriptive div
        var curVal = ($select.val() === '' || $select.val() === null) ? 'membLevelNone' : $select.val().trim();

        // Parse out the price and description for the summary box
        // Note that this will be very different when we have real data to work with
        var curFullDesc = $select.find('option:selected').text();
        var $aliasSchoolAffilPaymentSummaryBox = document.getElementById('aliasSchoolAffilPaymentSummaryBoxHidden').value;
        var hasAlias = false;
        if($aliasSchoolAffilPaymentSummaryBox != null && $aliasSchoolAffilPaymentSummaryBox != '' && $aliasSchoolAffilPaymentSummaryBox != ' ') {
        	hasAlias = true;
        }

        if (curVal === 'membLevelNone') {
        	if(hasAlias) {
        		$('#membCompleteInfo').html(olgDonor2.defaultMembershipMsg + ' for<br />' + $aliasSchoolAffilPaymentSummaryBox);
        	} else {
        		$('#membCompleteInfo').html(olgDonor2.defaultMembershipMsg);
        	}

        } else {
            var precision = 2;
            var amountFloat = parseFloat(curVal);
            if (amountFloat == Math.floor(amountFloat)) {
                precision = 0;
            }
            $('#amount').html('$' + amountFloat.toLocaleString(undefined, { maximumFractionDigits: precision, minimumFractionDigits: precision }));
        	if(hasAlias) {
                $('#membCompleteInfo').html($aliasSchoolAffilPaymentSummaryBox);
            } else {
            	$('#membCompleteInfo').html($('#membAffil').text());
            }

            var $addlGift = $('#membAddlGift');
            var addlAmount = ($addlGift.val()) ? $addlGift.val() * 1 : 0;
            var totalAmount = addlAmount + (curVal * 1);

            if (totalAmount == Math.floor(totalAmount)) {
                $('#amount_temp').val(totalAmount);
            } else {
                $('#amount_temp').val(totalAmount.toFixed(2));
            }
        }
    },

    // initializes pre-populated country information
    initCountries: function(){
        var $contactCountry = $('#contactCountry'),
            contactCountryCode = $contactCountry.val();

        if (contactCountryCode !== 'US') {
            this.setCountryFields(contactCountryCode, 'contact')
        }
    },

    countryAttributes: {
        'default': {
            'City': {
            	'label' : '<span>City</span><span aria-hidden="true"> *</span><span class="sr-only"> required</span>',
                'invalid-feedback' : 'City is required'
            },
            'State': {
            	'label' : '<span>State</span><span aria-hidden="true"> *</span><span class="sr-only"> required</span>',
                'props': {
                    'required': true,
                    'maxlength': 2
                },
                'data': {
                    'transform': 'uppercase'
                },
                'invalid-feedback' : 'State is required'
            },
            'PostCd': {
            	'label' : '<span>ZIP Code</span><span aria-hidden="true"> *</span><span class="sr-only"> required</span>',
                'invalid-feedback' : 'ZIP Code is required',
                'props': {
                    'pattern': '^\\d{5}(-\\d{4})?$',
                }
            }
        },
        'US': {
            'City': {
            	'label' : '<span>City</span><span aria-hidden="true"> *</span><span class="sr-only"> required</span>',
                'invalid-feedback' : 'City is required'
            },
            'State': {
            	'label' : '<span>State</span><span aria-hidden="true"> *</span><span class="sr-only"> required</span>',
                'invalid-feedback' : 'State is required',
                'props': {
                    'required': true,
                    'maxlength': 2
                },
                'data': {
                    'transform': 'uppercase'
                },
            },
            'PostCd': {
            	'label' : '<span>ZIP Code</span><span aria-hidden="true"> *</span><span class="sr-only"> required</span>',
                'invalid-feedback' : 'ZIP Code is required',
                'props': {
                    'pattern': '^\\d{5}(-\\d{4})?$',
                }
            }
        },
        'CA': {
            'City': {
            	'label' : '<span>City</span><span aria-hidden="true"> *</span><span class="sr-only"> required</span>',
                'invalid-feedback' : 'City is required'
            },
            'State': {
            	'label' : '<span>Province</span><span aria-hidden="true"> *</span><span class="sr-only"> required</span>',
                'invalid-feedback': 'Province is required',
                'props': {
                    'required': true,
                    'maxlength': 2
                }
            },
            'PostCd': {
               	'label' : '<span>Postal Code</span><span aria-hidden="true"> *</span><span class="sr-only"> required</span>',
                'invalid-feedback' : 'Postal Code is required',
                'props': {
                    'pattern': '^[ABCEGHJKLMNPRSTVXY]{1}\\d{1}[A-Z]{1} *\\d{1}[A-Z]{1}\\d{1}$'
                }
            }
        },
        'GB': {
            'City': {
            	'label' : '<span>Village</span><span aria-hidden="true"> *</span><span class="sr-only"> required</span>',
                'invalid-feedback' : 'Village is required'
            },
            'State': {
            	'label' : '<span>City/Locality</span>',
                'invalid-feedback': 'City/Locality is required',
                'props': {
                    'required': false,
                    'maxlength': 40
                },
                'data': {
                    'transform': ''
                }
            },
            'PostCd': {
               	'label' : '<span>Postal Code</span>',
                'props': {
                    'required': false,
                    'pattern': '.*?'
                }
            }
        },
        'INTL': {
            'City': {
            	'label' : '<span>City</span><span aria-hidden="true"> *</span><span class="sr-only"> required</span>',
                'invalid-feedback' : 'City is required'
            },
            'State': {
            	'label' : '<span>Region</span>',
                'invalid-feedback': 'State is required',
                'props':{
                    'required': false,
                    'maxlength': 40
                },
                'data': {
                    'transform': ''
                }
            },
            'PostCd': {
               	'label' : '<span>Postal Code</span>',
                'props': {
                    'required': false,
                    'pattern': '.*?',
                    'maxlength': 10
                }
            }
        }
    },


    setCountryFields: function(countryCode, addressType){
        var fieldCode,
        	errorMessagePrefix;

        switch (countryCode) {
            case "US":
            case "CA":
            case "GB":
                fieldCode = countryCode;
                break;
            default:
                fieldCode = 'INTL';
                break;
        }
        
        switch (addressType) {
	        case "contact":
	        	errorMessagePrefix = 'Contact ';
	        	break;
	        case "membGift":
	        	errorMessagePrefix = 'Gift Recipient '
	            break;
	        default:
	        	errorMessagePrefix = '';
	            break;
	    }

        _.each(
            $.extend(true, {}, this.countryAttributes['default'], this.countryAttributes[fieldCode]),
            function(fieldAttributes, fieldSelector){

                var $field = $('#' + addressType + fieldSelector);

                if (fieldAttributes['label']) {
                    $field.parent().find('label').html(fieldAttributes['label']);
                }

                if (fieldAttributes['invalid-feedback']) {
                    $field.parent().find('.js-invalid-feedback-msg').text(errorMessagePrefix + fieldAttributes['invalid-feedback']);
                }

                if (fieldAttributes['props']) {
                    $field.prop(fieldAttributes['props'])
                }

                if (fieldAttributes['data']) {

                    // jquery's .data() function does not apply data-atts correctly - manipulate raw attrs
                    _.each(fieldAttributes['data'], function(value, key, list){
                    	if (value == '') {
                    		$field.removeAttr('data-' + key);
                    	} else {
                    		$field.attr('data-' + key, value);
                    	}
                    });
                }
            }
        );
    },
    validatePhone: function(el,caption) {
        el.setCustomValidity('');
        $(el).siblings('.invalid-feedback').find('.js-invalid-feedback-msg').html('');
    	var phoneInput = $('#'+el.id);
    	var phoneNumber = phoneInput.val();
        var retVal = '';
    	if (phoneNumber && phoneNumber.trim().length > 0) {
    		phoneInput.val(phoneNumber.replace(/\D/g,''));
            var phoneLength = phoneNumber.replace(/\D/g,'').length;
            if (phoneLength < 8 || phoneLength > 15) {
  		  		retVal = "Invalid " + caption;
            }
        } else {
			if ($(el).attr('required')=='required') {
          		retVal = caption + " is required";
        	}
        }
        $(el).siblings('.invalid-feedback').find('.js-invalid-feedback-msg').html(retVal);
        return retVal;
    },
    validateDonorContactPhone: function(el,caption) {
        el.setCustomValidity('');
        $(el).siblings('.invalid-feedback').find('.js-invalid-feedback-msg').html('');
    	var phoneInput = $('#'+el.id);
    	var phoneNumber = phoneInput.val();
        var retVal = '';
    	if (phoneNumber && phoneNumber.trim().length > 0) {
    		phoneInput.val(phoneNumber.replace(/\D/g,''));
            var ctry = $('#contactCountry').select2('val');
            var phoneLength = phoneNumber.replace(/\D/g,'').length;
            if (ctry === 'US' || ctry === 'CA') {
                if (phoneLength != 10) {
                	retVal = "Valid US phone number is required";
                }
            } else {
                if (phoneLength < 8 || phoneLength > 15) {
                	retVal = "Valid international phone number is required";
                }
            }
        } else {
			if ($(el).attr('required')=='required') {
          		retVal = caption + " is required";
        	}
        }
        $(el).siblings('.invalid-feedback').find('.js-invalid-feedback-msg').html(retVal);
        return retVal;
    },
    
    validateEmail: function(el, caption) {
        el.setCustomValidity('');
        $(el).siblings('.invalid-feedback').find('.js-invalid-feedback-msg').html('');
    	var emailInput = $('#'+el.id);
    	var email = emailInput.val();
        var retVal = '';
    	if (email && email.trim().length > 0) {
    		if (!(/^[A-Za-z0-9!#$%&amp;\'*+\/=?^_`{|}~.-]+@[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])*(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])+)+$/.test(email))) {
    		  retVal = "Invalid " + caption;
    		}
        } else {
			if ($(el).attr('required')=='required') {
          		retVal = caption + " is required";
        	}
       	}
        $(el).siblings('.invalid-feedback').find('.js-invalid-feedback-msg').html(retVal);
        return retVal;
    },
    // Variable Initialization ================================================
    /**
     * variables set up for the signed and unsigned OLG fields
     * @return {void} just sets values
     */
    initVars: function() {

        this.signedFields = _.chain($('[data-field-signed]'))
            .map(function(field) {
                return $(field).attr('name')})
            .compact()
            .sortBy(function(fieldName){return fieldName})
            .value();

        this.unsignedFields = _.chain($('[data-field-unsigned]'))
            .map(function(field) {
                return $(field).attr('name')})
            .compact()
            .sortBy(function(fieldName){return fieldName})
            .value();

        this.merchantFields = _.chain($('[data-field-merchant]'))
            .map(function(field) {return $(field).attr('name')})
            .compact()
            .sortBy(function(fieldName){return fieldName})
            .value();

        // console.log('collected fields!', this.signedFields, this.unsignedFields, this.merchantFields)
    },

    // Handle other (non-gift amount) button descriptions
    // showDescription: function(target) {
    //     var selector =  $(target).closest('.your-description-group').data('descriptions') + ' ' +  $(target).data('target');
    //     if (selector) {
    //         var $target = $(selector);
    //         $target.siblings().addBack().stop().hide().removeClass('active');
    //         $target.addClass('active');
    //         $target.fadeIn(300);
    //     }
    // },
    checkForFormErrors: function() {
        'use strict';
        var foundBadField = false;

        // Execute any custom error handlers.  The handler should return an error string if there's a problem
        // or an empty string ('') if it's all good.
        // Each validity function must be a public memeber of this olgDonor2 object or the olgGift2 object.
        // The element is passed to the funtion
        $('[data-validation]').each(function(idx, el) {
            var functionName = $(el).data().validation;
            var caption = el.dataset?.caption;
            var validityFunc = olgDonor2[functionName] || olgGift2[functionName] || null;
            if (validityFunc) {
                el.setCustomValidity( validityFunc(el, caption) );
                el.checkValidity();
                //el.reportValidity();
            }
        });

        var $mainForm = $('#mainForm');
        $mainForm.removeClass('was-validated');
        $mainForm.find(":invalid").each(function(idx, el) {
            if (!foundBadField) {
                el.focus(); // This is the first bad element
                $mainForm.addClass('was-validated');
                foundBadField = true;
            }
        });
        var errorMessageString = '';
//        errorMessageString = $.map($('.was-validated .form-control:invalid~ .js-invalid-feedback-msg'), function(el,idx) {return el.innerText;}).join('<br>');
        errorMessageString = $.map($('.invalid-feedback:visible .js-invalid-feedback-msg'), function(el,idx) {return el.innerText;}).join('<br>');
        var hasErrors = errorMessageString.trim().length > 0;
        $('#error-msgs').html(hasErrors ? '<strong>Errors:</strong><br><div class="text-danger">' + errorMessageString + '</div>' : '');
        return hasErrors;
    },
    checkForCustomFieldErrors: function(el) {
        var validationFuncName = el.dataset?.validation;
        var caption = el.dataset?.caption;
        if (validationFuncName) {
            var validityFunc = olgDonor2[validationFuncName] || olgGift2[validationFuncName] || null;
            if (validityFunc) {
                validityFunc(el, caption)
            }
        }
    },
    checkForSigningErrors: function(myMsg) {

        function parseMessage(str){
            // typical format:
            // donor.membership.giftRecipientEmail: size must be between 0 and 25
            var parts = str.split(":");
            var type = parts[0].split('.');
            type = type[type.length-1];
            var note = parts[1];
            return type + ':' + note;
        }

        var mess = "";
        try {
            var serverMess = JSON.parse(myMsg.responseText);
            if (serverMess && serverMess["bindingErrors"]) {
                _.each(serverMess.bindingErrors, function(el){
                    mess+= (myMsg.status + " " + parseMessage(el["givingRequest.formFields"]));
                })
            } else {
                try {
                    mess+= (myMsg.status + " " + serverMess["throwable"].message);
                } catch (e) {
                    console.warn(serverMess);
                }
            }
        } catch (e) {
            mess = myMsg.status;
        }
    },


    checkForTimeoutErrors: function() {
        // ToDo: Make this a switch statement
        if (olgDonor2.cyberFormLoads == 0) {
            olgDonor2.errorList.push('Timeout Error waiting for Cybersource');
        }
        if (olgDonor2.cyberFormLoads == 1) {
            olgDonor2.errorList.push('Timeout Error waiting for our server');
        }
    },

    // Inform user of impending timeouts
    initializeUserTimeout: function() {
        var timeoutNoificationPeriod = olgTimeoutNotificationInterval;
        var timeoutExpiresPeriod = olgTimeoutExpiresInterval;
        olgDonor2.timerExpired = false;

        // The "session will timeout soon unless you click OK" message will appear after the notificationTimeout period
        olgDonor2.notificationTimeout = setTimeout(function() {
            $('#timeout-modal').modal({keyboard: false, backdrop: 'static'});
        }, timeoutNoificationPeriod);

        // The "session has timed-out" message will appear after the expiredTimeout period.  This should match the app server timeout period0
        olgDonor2.expiredTimeout = setTimeout(function() {
            olgDonor2.timerExpired = true;
            $('#timeout-modal').modal('hide');
            $('#timeout-expired-modal').modal({keyboard: false, backdrop: 'static'});
        }, timeoutExpiresPeriod);

        // Refresh the session no matter how the user closes the "timeout coming soon" box
        $('#timeout-modal').on('hide.bs.modal', function (e) {
            if (!olgDonor2.timerExpired) {
                olgDonor2.refreshUserSession(location.href);
            }
        });
    },


    // restarts the timeout clock by requesting the url specified (probably the same page that the user is currently on)
    refreshUserSession: function(myUrl) {
        clearTimeout(olgDonor2.notificationTimeout);
        clearTimeout(olgDonor2.expiredTimeout);
        $('#timeout-modal').off('hide.bs.modal');
        // ajax request goes here
        jQuery.ajax({url: myUrl})
            .done(function (data) {
                if (console && console.log) {
                    console.log('Session Refreshed');
                }
            })
            .fail(function (data) {
                // If this fails, then just put up the box that says it's expired and refresh the page when they click OK
                olgDonor2.timerExpired = true;
                $('#timeout-expired-modal').modal('show');
            });

        // Restart the countdown clocks
        olgDonor2.initializeUserTimeout();
    }
};


$(window).on('load', function () {
	olgDonor2.init();
})

'use strict';
/*
 * olgGift2.js
 *
 * Online giving form user interactions for Gift Amount buttons, other amount input, affiliate selection, and
 * fund selection.  Also handles multiple gift addition and removal.
 *
 */
console.log('In olgGift2.js');

var olgGift2 = function() {
    const MAX_GIFT_AMOUNT = 999999.99;
    const MAX_GIFT_AMOUNT_STRING = '$999,999.99';
    const MAX_GIFTS_PER_PAGE = 5;
    var fundDropDownMap = {};

    // Initialize Gift Amounts, Schools, Funds, and user interactions
    // Init is called from olgDonor2.js
    function init() {
        // Initialize fund dropdown options for select2 for additional gifts
        // each fundDropDownMap element contains a list of funds to be used by the Select2 funds drop-down
        _.map(top.uWideVisSchools, function(obj) {
            var funds = _.chain(obj.givingAllocations)
                .filter({uwideVisibility: true})  // make sure we're only getting the visible funds
                .sortBy('displayName')
                .map(function(fund) {             // Format it for Select2
                    return {id: fund.id, text: fund.displayName}
                })
                .value();
            fundDropDownMap[obj.code] = funds;
        });

        // Not sure if we'll need this when we get to the staff form
        // Expand any gift groups that already have values in them (staff form only)
        // var $el = null;
        // $('.your-gift-group').each(function (idx, el) {
        //     if (idx > 0) {
        //         $el = $(el);
        //         if ($el.find('input.donationAmt').val().trim().length > 0) {
        //             olgDonor2.giftsOpen[idx+1] = true;
        //             $el.show();
        //             $el.find('input').filter('.js-staff-fund, .js-staff-amt').prop({'required': true}).updatePolyfill();
        //             var $fundElement = $el.find('input[name^="fund_"]');
        //             //$fundElement.select2('destroy');
        //             //olgDonor2.setupStaffFundSelect($fundElement);
        //             $el.find('div[id^="appeal_section_"]').hide();
        //         } else {
        //             olgDonor2.giftsOpen[idx+1] = false;
        //         }
        //     }
        // });


        // -------------------------------------------------------------------------------------------------------------
        // Set Up Events
        // -------------------------------------------------------------------------------------------------------------
        // Add additional gift button
        $('#add-gift-btn').click(addGift);

        // -------------------------------------------------------------------------------------------------------------
        // These events will only work on the first (main) gift - not on the additional gifts
        $('#your-gift-group-1')
            .on('focusin', '.js-gift-other-amt', function(ev){
                showGiftDescription(ev.target); // This shows an empty description for the "Other Amount" field.
            })
            .on('focusin', '.js-amt-button', function(ev){
                showGiftDescription(ev.target); // Descriptions only appear on the first gift.
            });

        // -------------------------------------------------------------------------------------------------------------
        // These events will work on all gifts, including the additional ones
        $('#js-all-gifts')
            .on('change', '.js-amt-button', function(ev){
                // clear out 'Other Amount' field when a radio button is checked
                var $targetBtn = $(ev.target);
                $targetBtn.closest('label').siblings().find('.js-gift-other-amt').val('');

                // Set the "mostly-hidden" donationAmt to the selected button's amount
                var $donationAmountControl = $targetBtn.closest('.your-gift-group').find('.donationAmt');
                $donationAmountControl.val($targetBtn.val());

                // Call the validation function (if there's one specified in the donationAmt's data-validation attribute)
                var validationFuncName = $donationAmountControl.data().validation;
                if (validationFuncName) {
                    var validityFunc = olgDonor2[validationFuncName] || olgGift2[validationFuncName] || null;
                    if (validityFunc) {
                        $donationAmountControl[0].setCustomValidity( validityFunc($donationAmountControl) );
                        $donationAmountControl[0].checkValidity();
                    }
                }

                olgDonor2.updateSummary(); // Update the summary box

                // Mark the correct fund as selected.  For some reason, this wasn't sticking when done within
                // this method.  Doing it in a setTimeout solved this.
                // This is needed so that when we tab away from the radio buttons, and then tab back, we're placed on
                // the selected element.
                setTimeout(function() {
                    $targetBtn.prop( "checked", true );
                }, 0);
            })
            .on('change', '.js-gift-other-amt', function(ev){
                var $myButtons =  $(ev.target).closest('.js-button-row').find('.js-amt-button')
                var $this = $(ev.target); 
                var inputString = $this.val();
                inputString = inputString.replace(/[^0-9\.]+/g, "");
                var input = isNaN(inputString) ? 0 : Number(inputString);
                // Convert Input with a single decimal place to have 2 decimal places
                var isSingleDecimal = (input != Math.floor(input) && input * 10 == Math.floor(input * 10));
                if (input) {
                    $myButtons.prop('checked', false);
                    $this.closest('.js-button-row').find('.active').removeClass('active');
                }

                var $donationAmountControl = $this.closest('.your-gift-group').find('.donationAmt');
                $donationAmountControl.val(input);

                var validationFuncName = $donationAmountControl.data().validation;
                var validityFunc = olgDonor2[validationFuncName] || olgGift2[validationFuncName] || null;
                if (validityFunc) {
                    $donationAmountControl[0].setCustomValidity( validityFunc($donationAmountControl) );
                    $donationAmountControl[0].checkValidity();
                }

                $this.val(function () {
                    return input == 0 ? "" : input.toLocaleString("en-US") + (isSingleDecimal ? + '0' : ''); 
                }); 

                olgDonor2.updateSummary();
            })
            .on('select2:select', 'select[data-validation].init-select2', function(ev) {
                var el = ev.target;
                var functionName = $(el).data().validation;
                var validityFunc = olgDonor2[functionName] || olgGift2[functionName] || null;
                if (validityFunc) {
                    el.setCustomValidity( validityFunc(el) );
                    el.checkValidity();
                }
            })
            .on('select2:select', '.js-fund', setGiftCommentVisibility)
            .on('select2:select', '.js-schoolSelect', function(ev) {
                var $school = $(ev.target);
                var $fund = $school.closest('.your-gift-group').find('.js-fund');
                var fundData = [];

                if ($school.attr('type') !== 'hidden') { // This can only be hidden on gift #1
                    var schoolCode = $school.val();
                    if(schoolCode) {
                        $fund.attr('disabled', false);

                        // Refill the fund list
                        if (schoolCode) {
                            fundData = fundDropDownMap[ schoolCode ];
                        }
                        if (!(fundData.length && fundData[0].id === '')) {
                            fundData.unshift({id: '', text: 'Select a Fund'});
                        }

                        $fund.empty().trigger('change');
                        $fund.select2({
                            data: fundData
                        });
                    } else {
                        $fund.empty().trigger('change');
                        fundData[0] = {id: '', text: 'Select a Fund'}
                        $fund.select2({
                            data: fundData
                        });
                        $fund.attr('disabled', true);
                    }
                } else {
                    $fund.attr('disabled', false);
                }
                olgDonor2.updateSummary();
            })
            .on('click', '.js-delete-gift', removeGift)
            .on('hidden.bs.collapse', '.your-gift-group', function(ev) {
                // Clean up the Select2 elements
                var $amountSection = $(ev.target);
                $amountSection.find('.js-schoolSelect').select2('destroy');
                $amountSection.find('.js-schoolSelect').off('select2:select');
                $amountSection.find('.js-fund').select2('destroy');
                $amountSection.find('.js-fund').off('select2:select');

                // Lose the div with the additional gift
                $amountSection.collapse('dispose');
                $amountSection.remove();

                // Update info
                olgDonor2.updateSummary();
                olgDonor2.initVars(); // update signed/unsigned/merchant field collections
            })
            .on('shown.bs.collapse', '.your-gift-group', function(ev) {
                $(ev.target).find('.js-schoolSelect').focus();
            })
        ;

        // ToDo: Move $('#add-staff-gift-btn').click stuff from olgDonor2.js to here
    } // end of init function

    // -----------------------------------------------------------------------------------------------------------------
    // Handle the gift amount descriptions
    function showGiftDescription(target) {
        var selector =  $(target).closest('.your-gift-group').data('descriptions') + ' ' +  $(target).data('target');
        if (selector) {
            var $target = $(selector);
            $target.siblings().addBack().stop().hide().removeClass('active');
            $target.addClass('active');
            $target.fadeIn(300);
        }
    }

    // -----------------------------------------------------------------------------------------------------------------
    // Handle multiple gift entry
    function addGift(ev){
        // Shows max gifts error in the summary box and exits
        if ($('.your-gift-group').length >= MAX_GIFTS_PER_PAGE) {
            $('.max-gifts').collapse('show');
            // alert($('.max-gifts').text().trim());
            return;
        }

        var ctr = $('#maxGiftNumber').val();
        var ctr = parseInt(ctr) + 1;

        // Set up delimiters "{{...}}" for code, "{{=...}}" for expressions
        _.templateSettings = {
            evaluate: /\{\{(.+?)\}\}/g,
            interpolate: /{{=([\s\S]+?)}}/g
        };
        // generate new 'gift-group' from template (new gift is created with class='collapsed')
        var $giftInfo =  _.template($('#addlGiftAmts').html())({counter: ctr});

        // Store the new max gift number index
        $('#maxGiftNumber').val(ctr);

        // Add it to the end of the gift amounts section
        $('#js-all-gifts').append($giftInfo);

        // Initialize the drop-downs
        $('#school_' + ctr).select2();
        $('#fund_' + ctr).select2();
        $('#fund_' + ctr).attr('disabled', true); // Start the fund as disabled until a school is selected

        // Show the additional gift
        $('#your-gift-group-' + ctr).collapse('show');
        
        // Update the list of signed and merchant-defined fields and the summary box
        olgDonor2.initVars();
        olgDonor2.updateSummary();
    }

    function removeGift(ev) {
        $(ev.target).closest('.your-gift-group').collapse('hide');
        // Most of this task is ow in .on('hidden.bs.collapse',...) above
    }

    function getLastGift(el) {
        var giftElements = $('.donationAmt');
        return giftElements[giftElements.length - 1];
    }
    // -----------------------------------------------------------------------------------------------------------------
    // Custom validation functions
    // Need to make sure that an amount was added using either buttons or the Other Amount field.  This is attached to
    // the "mostly-hidden" gift amount field
    function validateGiftAmt(el) {
        var retVal = '';
        var myData = $(el).val();
        if (!myData) {
            retVal = 'A gift amount is required';
        } else {
            var amount = Number(myData);
            if (amount < 10) {
                retVal = 'Minimum gift amount is $10';
            } else if (amount > MAX_GIFT_AMOUNT) {
                retVal = 'Maximum gift amount is ' + MAX_GIFT_AMOUNT_STRING;
            }
        }
        if (retVal.length == 0
                && el == getLastGift(el)
                && areAllGiftsBelowMaxGiftAmount()
                && getTotalAmountOnGiftForm() > MAX_GIFT_AMOUNT ) {
            retVal = 'Total amount of all gifts may not exceed ' + MAX_GIFT_AMOUNT_STRING;
        }
        $(el).siblings('.invalid-feedback').children('.js-gift-amt-msg').html(retVal);
        return retVal;
    }
    
    function validateMembAddlGiftAmt(el) {
        var retVal = '';
        var myData = $(el).val();
        myData = myData.replace(/[^0-9\.]+/g, ""); 
        if (myData) {
            var amount = Number(myData);
            if (amount < 10) {
                retVal = 'Minimum gift amount is $10';
            } else if (amount > MAX_GIFT_AMOUNT) {
                retVal = 'Maximum gift amount is ' + MAX_GIFT_AMOUNT_STRING;
            }
        }

        if (retVal.length == 0 && getTotalAmountOnMembershipForm() > MAX_GIFT_AMOUNT) {
            retVal = 'Total amount of all gift and membership may not exceed ' + MAX_GIFT_AMOUNT_STRING;
        }
        $(".js-memb-addl-gift-amt-msg").html(retVal);
        return retVal;
    }
    
    function validateRecurringChargeConsentFlag(el) {
        var retVal = '';
        if ($('#recurring-btn').is(':checked') && !el.checked) {
            retVal = 'Recurring payments require consent';
        }
        $(".js-recurring-charge-feedback-msg").html(retVal);
        return retVal;
    }

    function validatePaymentFrequency(el) {
        var retVal = '';
        if ($('#recurring-btn').is(':checked') && el.value=='') {
            retVal = 'Payment Frequency is required';
        }
        $(".js-payment-frequency-feedback-msg").html(retVal);
        return retVal;
    }
    

    // Make sure a school is selected from the select2 control
    function validateSchool(el) {
        var retVal = '';
        var myData = $(el).select2('data')
        if (!myData[0].id) {
            retVal = 'School is Required';
        }
        return retVal;
    }

    // Make sure a fund is selected from the select2 control
    function validateFund(el) {
        var retVal = '';
        var myData = $(el).select2('data')
        if (myData.length > 0 && !myData[0].id) {
            retVal = 'Fund is Required';
        }
        return retVal;
    }

    function getAdditionalGiftOnMembershipForm() {
        var inputString = $('#membAddlGiftInput').val().replace(/[^0-9\.]+/g, "");
        return isNaN(inputString) ? 0 : Number(inputString);
    }

    function getTotalAmountOnMembershipForm() {
        var addlGift = getAdditionalGiftOnMembershipForm(); 
        return ($('#membLevel').val() * 1) + addlGift;
    }
    
    function areAllGiftsBelowMaxGiftAmount() {
        var $donationFields = $('.donationAmt');
        return  _.chain($donationFields)
            .map(function(el){ return $(el).val() ? Number($(el).val()) : 0 })
            .max() < MAX_GIFT_AMOUNT;
    }
    
    function getTotalAmountOnGiftForm() {
        var $donationFields = $('.donationAmt');
        return  _.chain($donationFields)
            .map(function(el){ return $(el).val() ? Number($(el).val()) : 0 })
            .sum()
            .value();
    }

    // -----------------------------------------------------------------------------------------------------------------
    // Updates info in the summary box
    function updateGiftSummary() {
        var tot = 0;
        var htmlText = '';
        var $school;
        var schoolNames = [];

        var $donationFields = $('.donationAmt');

        if ($donationFields.length < MAX_GIFTS_PER_PAGE) {
            $('.max-gifts').collapse('hide');
        }

        // Which schools have donated to this gift
        schoolNames = _.map($donationFields, function(el) {
            var retVal = '';
            var $school = $(el).closest('.your-gift-group').find('select.js-schoolSelect, input.js-school');

            if ($school.val()) {
                if ( $school.attr('type') === 'hidden' ) {  // if pre-selected, get the school name from the hidden field
                    retVal = $school.data().levelname;
                } else if ($school.select2('data')) {       // Otherwise, get it from the combo-box
                    retVal = $school.select2('data')[0].text
                }
            }
            return retVal;
        });

        var uniqueSchoolNames = _.chain(schoolNames)
            .without('',null)
            .uniq()
            .value();

        tot = getTotalAmountOnGiftForm();

        if (tot > 0) {
            var precision = 2;
            if (tot == Math.floor(tot)) {
                precision = 0;
            }
            $('#amount').html('$' + tot.toLocaleString(undefined, { maximumFractionDigits: precision, minimumFractionDigits: precision }));
            $('#amount_temp').val(tot.toFixed(2));

            if (uniqueSchoolNames.length == 1) {
                var giftSummaryCaption = $('#giftCompleteInfo').attr("giftSummaryCaption");
                htmlText = uniqueSchoolNames[0]? 'to ' + (giftSummaryCaption.length > 1 ? giftSummaryCaption : uniqueSchoolNames[0]) : 'Please Select a Recipient';
            } else {
                if (!uniqueSchoolNames.length) {
                    htmlText = 'Please Select a Recipient';
                } else {
                    if(uniqueSchoolNames.length > 1) {
                        htmlText = 'to multiple Schools/Affiliates';
                    }
                }
            }

            if ($('#giftFreq').val() !== 'oneTime') {
                var period = $('#giftFreqPeriod').val();
                if (period && period !== '') {
                    htmlText += '<br />recurring ' + period;
                }
            }
        } else {
            $('#amount').html('');
            $('#amount_temp').val(0);
            htmlText = olgDonor2.defaultSummaryMsg;
        }

        // Some code to make the giftCompleteInfo change sizes smoothly as the affiliate name changes
        // Note that there is a CSS transition on max-height, as transitions on height don't work
        // var giftCompleteElement = $('#giftCompleteInfo')[0];
        // var giftCompleteStyles = window.getComputedStyle(giftCompleteElement); // This is live and changes as the style changes

        // Initialize
        $('#giftCompleteInfo').html(htmlText);
        /*
        giftCompleteElement.style.height = '';
        giftCompleteElement.style.transition = 'none';
        giftCompleteElement.style.maxHeight = '1000px';

        var startHeight = giftCompleteStyles.height;
        var endHeight = giftCompleteStyles.height;
        giftCompleteElement.style.maxHeight = startHeight;
        giftCompleteElement.style.height = (startHeight > endHeight) ? startHeight : endHeight;

        setTimeout(function() {
            giftCompleteElement.style.transition = 'max-height 0.3s ease-out';
            giftCompleteElement.style.maxHeight = endHeight;
        }, 0);
        */


        // update the hidden frequency fields  ToDo: What is this?
        // this.changeFrequency(false); // passing 'false' prevents changeFrequency() from calling back to this
    };

    //------------------------------------------------------------------------------------------------------------------
    // Handle showing the "other fund info" field when OTH is selected for a fund
    function setGiftCommentVisibility() {
        var foundOther = checkForOtherGifts();

        if (foundOther) {
            $('#giftComment').collapse('show');
            $('#giftComment').attr('aria-expanded','true');
        } else {
            $('#giftComment').collapse('hide');
            $('#giftComment').attr('aria-expanded','false');
        }

        setGiftCommentRequired(foundOther);
    };

    function checkForOtherGifts() {
        var hasOther = false,
            $funds = $('.js-fund').filter('input, select');

        $funds.each(function(index, element){
            var value = $(element).val();

            if (value && value.indexOf('999') === 0) {
                hasOther = true;
                return false; // break loop
            }
        });

        return hasOther;
    };

    function setGiftCommentRequired(isRequired) {
        var $giftComment = $('#giftDetails');
        $giftComment.prop({'required': isRequired,});
    };

    return {
        init: init,
        addGift: addGift,
        getAdditionalGiftOnMembershipForm: getAdditionalGiftOnMembershipForm,
        getTotalAmountOnMembershipForm: getTotalAmountOnMembershipForm,
        validateFund: validateFund,
        getLastGift: getLastGift,
        validateRecurringChargeConsentFlag: validateRecurringChargeConsentFlag,
        validatePaymentFrequency: validatePaymentFrequency,
        validateGiftAmt: validateGiftAmt,
        validateMembAddlGiftAmt: validateMembAddlGiftAmt,
        updateGiftSummary: updateGiftSummary, 
        setGiftCommentVisibility: setGiftCommentVisibility // ACE-5219 JS
    }
}();
