PageSourceSearch

https://enroll.experiencegla.com/static/js/portal-scripts.js?v=55

js experiencegla.com collected 2026-09-24 09:22:47 UTC 62,916 bytes, 1,377 lines download raw bytes

1/**
2 * static/js/portal-scripts.js
3 *
4 * Scripts for GLA Portal Section Only
5 *
6 */
7
8var MYCACHEBUSTER = 1;
9
10// Define some globals if they are not defined yet (parent HTML file should define these)
11if (typeof applicationScodeUnformattedPrice === "undefined") {
12    var applicationScodeUnformattedPrice = "";
13}
14if (typeof applicationScodeFormattedPrice === "undefined") {
15    var applicationScodeFormattedPrice = "";
16}
17if (typeof applicationDiscountAppliesToDueNow === "undefined") {
18    var applicationDiscountAppliesToDueNow = false;
19}
20if (typeof applicationScodeUnformattedDueNowPrice === "undefined") {
21    applicationScodeUnformattedDueNowPrice = "";
22}
23if (typeof applicationScodeFormattedDueNowPrice === "undefined") {
24    applicationScodeFormattedDueNowPrice = "";
25}
26if (typeof applicationDiscountPercent === "undefined") {
27    applicationDiscountPercent = 0;
28}
29
30(function ($) {
31    // Polyfill for browsers w/o HTML5 form validation (*cough* Safari )
32    if($.webshims) { $.webshims.polyfill('forms'); }
33
34    $(function () {
35        var $line_item_template = $('.line-item.trip').first().clone();
36        var selected_products = [];
37
38        // Grab data out of the multi value field
39        if ( $('#id_products').val() != '' ) {
40            $('#id_products :selected').each(function(i, option){
41                toggleTrip($(option).val(), 'add', true);
42            });
43            // Update Scholarship if it is applied and is discount percent
44            if ($('#scholarship-discount-percent').length) {
45                applicationDiscountPercent = parseFloat($('#scholarship-discount-percent').text());
46            }
47        }
48
49        $(document).ready(function() {
50            if (typeof ENROLL_IS_GROUP !== 'undefined' && ENROLL_IS_GROUP === 'True') {
51                if (typeof ENROLL_IS_CHAPERONE !== 'undefined' && ENROLL_IS_CHAPERONE === 'True') {
52                    // Convert placeholders for signature fields to use "Chaperone" text
53
54                    // Terms and conditions form
55                    if ($('#id_parent_signature_terms').length) {
56                        var parentSigTermsEle_Placeholder = $('#id_parent_signature_terms').attr('placeholder');
57                        var chaperoneSigTermsEle_Placeholder = parentSigTermsEle_Placeholder.replace('Parent', 'Chaperone');
58                        $('#id_parent_signature_terms').attr('placeholder', chaperoneSigTermsEle_Placeholder);
59                    }
60
61                    // Medical Form
62                    if ($('#id_parent_signature_medical').length) {
63                        var parentSigMedicalEle_Placeholder = $('#id_parent_signature_medical').attr('placeholder');
64                        var chaperoneSigMedicalEle_Placeholder = parentSigMedicalEle_Placeholder.replace('Parent', 'Chaperone');
65                        $('#id_parent_signature_medical').attr('placeholder', chaperoneSigMedicalEle_Placeholder);
66                        var parentSigMedicalLabelText = $('label[for="id_parent_signature_medical"]').text();
67                        var chaperoneSigMedicalLabelText  = parentSigMedicalLabelText.replace('Parent', 'Chaperone');
68                        $('label[for="id_parent_signature_medical"]').text(chaperoneSigMedicalLabelText);
69                    }
70                }
71
72                // Code of conduct page change placeholder for student signature
73                if ($('#id_student_signature_coc').length) {
74                    var studentSigCocEle_Placeholder = $('#id_student_signature_coc').attr('placeholder');
75                    var travelerSigCocEle_Placeholder = studentSigCocEle_Placeholder.replace('Student', 'Traveler');
76                    $('#id_student_signature_coc').attr('placeholder', travelerSigCocEle_Placeholder);
77                    var studentSigCocLabelText = $('label[for="id_student_signature_coc"]').text();
78                    var travelerSigCocLabelText  = studentSigCocLabelText.replace('Student', 'Traveler');
79                    $('label[for="id_student_signature_coc"]').text(travelerSigCocLabelText);
80                }
81            }
82        });
83
84        // Set product selection based on Select button click
85        $(document).on("click", '.product-select > a', function(e){
86            e.preventDefault();
87            var product_id = $(this).attr('href').split('-')[1];
88
89            if ( $(this).hasClass('select-button') ) {
90                toggleTrip(product_id, 'add');
91            } else {
92                toggleTrip(product_id, 'remove');
93            }
94        });
95
96        $(document).on("click", '.product-date-select-buttons > a', function(e){
97            e.preventDefault();
98            var parentEle = $(this).parent();
99
100            if ($(this).hasClass('date-select-button')) {
101                parentEle.parent().find('td > .product-date-select-section').show();
102                parentEle.closest('.product-name-grouping').removeClass('collapsed').addClass('expanded');
103                parentEle.addClass('selected');
104            } else {
105                parentEle.removeClass('selected');
106                parentEle.closest('.product-name-grouping').removeClass('expanded').addClass('collapsed');
107                parentEle.parent().find('td > .product-date-select-section').hide();
108            }
109        });
110
111        // In line items table remove link
112        $(document).on("click", '.line-item a.remove', function(e){
113            e.preventDefault();
114            var product_id = $(this).attr('href').split('-')[1];
115            toggleTrip(product_id, 'remove');
116        });
117
118        // Smooth Scroll to Element on the Page
119        function scrollTo(target_selector) {
120            $('html,body').animate({
121                scrollTop: $(target_selector).offset().top
122            }, 1000);
123            return false;
124        }
125
126        function formatScholarshipMoney(amount) {
127            return accounting.formatMoney(amount, {
128                symbol: '$',
129                precision: 2,
130                thousand: ',',
131                decimal: '.',
132                format: {
133                    pos: '%s%v',
134                    neg: '-%s%v',
135                    zero: '%s%v'
136                }
137            });
138        }
139
140        // Check if value is a date
141        function isDate(date) {
142            return (new Date(date) !== "Invalid Date" && !isNaN(new Date(date))) ? true : false;
143        }
144
145        // Check if a trip has start & end dates
146        function tripHasDates($trip) {
147            return ( isDate($trip.data('startdate')) && isDate($trip.data('enddate')) ) ? true : false;
148        }
149
150        // Check if a date range overlaps
151        // via http://stackoverflow.com/questions/22784883/check-if-more-than-two-date-ranges-overlap
152        function dateRangeOverlaps(a_start, a_end, b_start, b_end) {
153            if (a_start <= b_start && b_start <= a_end) return true; // b starts in a
154            if (a_start <= b_end   && b_end   <= a_end) return true; // b ends in a
155            if (b_start <  a_start && a_end   <  b_end) return true; // a in b
156            return false;
157        }
158
159        // Check if trip's start and end dates overlap with an already added trip
160        function areDatesOverlapping($trip_a) {
161            if ( selected_products.length > 0 && tripHasDates($trip_a) ) {
162                var start_a = Date.parse($trip_a.data('startdate'));
163                var end_a = Date.parse($trip_a.data('enddate'));
164                var has_overlap = false;
165
166                $.each(selected_products, function() {
167                    var $trip_b = $('#product-' + this);
168                    if (tripHasDates($trip_b) && dateRangeOverlaps(start_a, end_a, Date.parse($trip_b.data('startdate')), Date.parse($trip_b.data('enddate'))) ) {
169                        has_overlap = true;
170                        return false;
171                    }
172                });
173                return has_overlap;
174            } else {
175                return false;
176            }
177        }
178
179        // Check if user is trying to choose more than one program where at least one is a group program
180        function checkGroupProgram($trip_a) {
181            if ( selected_products.length > 0 ) {
182                var failedCheck = false;
183                $.each(selected_products, function() {
184                    var $trip_b = $('#product-' + this);
185                    var isGroup = $trip_a.hasClass('group');
186                    if ($trip_b.hasClass('group') != isGroup) {
187                        failedCheck = true;
188                        return true;
189                    }
190                });
191                return failedCheck;
192            } else {
193                return false;
194            }
195        }
196
197        function slugifyDestinationName(name) {
198            return String(name || '')
199                .toLowerCase()
200                .trim()
201                .replace(/[^a-z0-9]+/g, '-')
202                .replace(/^-+|-+$/g, '');
203        }
204
205        function getDestinationContextByProductId(productId) {
206            var product_basic_object = null;
207
208            for (var i = 0; i < product_to_destination_list.length; i++) {
209                if (product_to_destination_list[i].product_id == productId) {
210                    product_basic_object = product_to_destination_list[i];
211                    break;
212                }
213            }
214
215            if (!product_basic_object) {
216                return null;
217            }
218
219            var destination_slug = '';
220            var is_special = false;
221
222            if (product_basic_object.country_name == 'Multi-Program') {
223                destination_slug = 'multi-programs';
224                is_special = true;
225            } else if (product_basic_object.country_name == 'Special Offers') {
226                destination_slug = 'special_offers';
227                is_special = true;
228            } else if (product_basic_object.country_name == 'Fellowships') {
229                destination_slug = 'fellowships';
230                is_special = true;
231            } else if (product_basic_object.country_name == 'Add-ons') {
232                destination_slug = 'addons';
233                is_special = true;
234            } else {
235                destination_slug = slugifyDestinationName(product_basic_object.country_name);
236                if (product_basic_object.group) {
237                    destination_slug += '_group';
238                }
239            }
240
241            var targetYear = is_special
242                ? window.backendPayload.primary_year.toString()
243                : String(product_basic_object.year || window.backendPayload.primary_year);
244            var yearData = is_special ? null : window.backendPayload.data_by_year[targetYear];
245
246            return {
247                destination_slug: destination_slug,
248                is_special: is_special,
249                yearData: yearData,
250                targetYear: targetYear
251            };
252        }
253
254        function ensureProductRowLoaded(productId) {
255            var $trip = $('#product-' + productId);
256            if ($trip.length) {
257                return $trip;
258            }
259
260            var destinationContext = getDestinationContextByProductId(productId);
261            if (!destinationContext) {
262                return $trip;
263            }
264
265            addTableToDestination(
266                destinationContext.destination_slug,
267                destinationContext.yearData,
268                destinationContext.targetYear
269            );
270
271            return $('#product-' + productId);
272        }
273
274        // Adds or removes a trip
275        function toggleTrip(id, action, prevent_input) {
276            var $trip = $('#product-' + id);
277
278            // If trip is not on the DOM yet, then we must add it
279            if (!$trip[0]) {
280                var destinationContext = getDestinationContextByProductId(id);
281                if (destinationContext) {
282                    addTableToDestination(
283                        destinationContext.destination_slug,
284                        destinationContext.yearData,
285                        destinationContext.targetYear
286                    );
287                }
288
289                $trip = $('#product-' + id);
290            }
291
292            // Add
293            if ( action == 'add' || (!action && !$trip.hasClass('picked')) ) { // Can toggle if no action specified
294                if (areDatesOverlapping($trip)) {
295                    alert("This trip overlaps another trip you selected. \r\n\nTo add this trip, please remove trips with overlapping dates first.");
296                    return false;
297                } else if (checkGroupProgram($trip)) {
298                    alert("You cannot choose both a group trip and a non-group trip. \r\n\nTo add this trip, please remove problematic trips.");
299                    return false;
300                } else {
301                    $trip.find('.product-select').addClass('selected');
302                    selected_products.push(id);
303
304                    if ( !prevent_input ) {
305                        $('#id_products').find("option[value="+id+"]").prop("selected", true);
306                        scrollTo('#create-your-account');
307                    }
308                }
309                // Remove
310            } else {
311                $trip.find('.product-select').removeClass('selected');
312                selected_products.splice(selected_products.indexOf(id), 1); // Removes product from array
313                $('#lineitem-' + id).slideUp(400); // Hides row in totals
314
315                if ( !prevent_input ) {
316                    $('#id_products').find("option[value="+id+"]").prop("selected", false);
317                }
318            }
319            showOrHideGroupFields();
320            highlightPicked();
321            buildLineItems();
322            updateTotals();
323        }
324
325        // var originalOptions = $('#id_birthdate_year').html();
326        var originalWhyDoINeedAnAccountText = $('#why-do-i-need-an-account').html();
327
328        // Shows or hides group fields depending on group enrollment or not.
329        function showOrHideGroupFields() {
330            if (selected_products.length > 0 && $('#product-' + selected_products[0]).hasClass('group')) {
331                // Group enrollment, update form accordingly.
332                updateFormForGroupEnrollment();
333            } else {
334                // Non-Group enrollment, use default student values here
335                updateFormForNonGroupEnrollment();
336            }
337        }
338
339        /**
340         * Updates the enrollment form fields to be used for a group enrollment option.
341         */
342        function updateFormForGroupEnrollment() {
343            // Change label of student email address field
344            $('label[for="id_email"]').html('Traveler\'s Email Address');
345
346            // Update help text on the right hand side of step 2
347            var newWhyDoINeedAnAccountText = originalWhyDoINeedAnAccountText;
348            // Replace 'student' with 'traveler'
349            if (newWhyDoINeedAnAccountText) {
350                newWhyDoINeedAnAccountText = newWhyDoINeedAnAccountText.replace(new RegExp('student', 'g'), 'traveler');
351                $('#why-do-i-need-an-account').html(newWhyDoINeedAnAccountText);
352            }
353
354            // Change label of contact info step (3) personal info portion
355            $('#enrollment_contact-info_personal-info-header').html('Traveler\'s Information');
356
357            // Change label of school question
358            $('label[for="id_name_of_school_attending"]').html('Name of your school or organization');
359
360            // Remove T-shirt size option
361            $('#id_t_shirt_size').parent('.form-group').css('display', 'none');
362            $('#id_t_shirt_size').parent('.form-group').removeClass('required');
363            $('#id_t_shirt_size').prop('required', '');
364
365            // Show is_chaperone checkbox
366            $('#is_chaperone_checkbox_wrapper').show();
367
368            // // Change dropdown options for year of birth (need to save the previous options in-memory)
369            // var options = '<option value="" selected="selected">-- Year --</option>';
370            // var currentYear = (new Date()).getFullYear();
371            // for (var year = currentYear; year >= 1900; year--) {
372            //     options = options
373            //         + '<option value="'
374            //         + year
375            //         + '">'
376            //         + year
377            //         + '</option>';
378            // }
379            // $('#id_birthdate_year').html(options);
380
381            // Change parent to emergency contact
382            $('#primary-parent-info').html('Emergency Contact');
383            $('label[for=id_parent_email]').html('Emergency Contact\'s Email Address');
384            $('label[for=id_add_second_parent]').html('Add a second emergency contact?');
385            $('#secondary-parent-info').html('Secondary Emergency Contact');
386            $('label[for=id_secondary_parent_email]').html('Emergency Contact\'s Email Address');
387            $('.parent-email-different-text').html(
388                'The emergency contact\'s email address must be different from the'
389                + ' traveler\'s email address. Both must be valid since you will'
390                + ' receive important communications related to your program'
391                + ' at this address.'
392            );
393
394            // Change the student address label
395            $('#student-address-header').html('Traveler\'s Permanent Address');
396
397            // Hide secondary parent address form
398            $('#second-parent-address').css('display', 'none');
399            $('#second-parent-address .form-group').removeClass('required');
400            $('#second-parent-address .form-group :input').prop('required', false);
401
402            // // Add event handler to hide parent address if student over 18
403            // var birthdate = new Date();
404            // $('#id_birthdate_month').on('change', function() {
405            //     birthdate.setMonth($('#id_birthdate_month').val());
406            //     renderParentAddressDependingOnBday(birthdate);
407            // });
408            // $('#id_birthdate_day').on('change', function() {
409            //     birthdate.setDate($('#id_birthdate_day').val());
410            //     renderParentAddressDependingOnBday(birthdate);
411            // });
412            // $('#id_birthdate_year').on('change', function() {
413            //     birthdate.setYear($('#id_birthdate_year').val());
414            //     renderParentAddressDependingOnBday(birthdate);
415            // });
416            // // Default to what is selected already
417            // if ($('#id_birthdate_month').val()) {
418            //     birthdate.setMonth($('#id_birthdate_month').val());
419            // }
420            // if ($('#id_birthdate_day').val()) {
421            //     birthdate.setDate($('#id_birthdate_day').val());
422            // }
423            // if ($('#id_birthdate_year').val()) {
424            //     birthdate.setYear($('#id_birthdate_year').val());
425            // }
426            // renderParentAddressDependingOnBday(birthdate);
427
428            // If no parent, do nothing
429            if (typeof no_parent != 'undefined' && no_parent == 'True') {
430                $('.parent-emergency-contact-fields').css('display', 'block');
431                $('#primary-parent-address .form-group').addClass('required');
432                $('#primary-parent-address .form-group :input').prop('required', true);
433                $('.parent-emergency-contact-address-fields').css('display', 'block');
434                $('.parent-emergency-contact-address-fields .form-group').addClass('required');
435                $('.parent-emergency-contact-address-fields .form-group :input').prop('required', true);
436            }
437            // If no emergency contact, show emergency contact form
438            if (typeof no_emerg != 'undefined' && no_emerg == 'True') {
439                $('.parent-emergency-contact-fields').css('display', 'block');
440                $('#primary-parent-address .form-group').addClass('required');
441                $('#primary-parent-address .form-group :input').prop('required', true);
442                $('.parent-emergency-contact-address-fields').css('display', 'none');
443                $('.parent-emergency-contact-address-fields .form-group').removeClass('required');
444                $('.parent-emergency-contact-address-fields .form-group :input').prop('required', false);
445            } else {
446                // If have both, then don't show anything
447                $('.parent-emergency-contact-fields').css('display', 'none');
448                $('#primary-parent-address .form-group').removeClass('required');
449                $('#primary-parent-address .form-group :input').prop('required', false);
450                $('.parent-emergency-contact-address-fields').css('display', 'none');
451                $('.parent-emergency-contact-address-fields .form-group').removeClass('required');
452                $('.parent-emergency-contact-address-fields .form-group :input').prop('required', false);
453            }
454        }
455
456        /**
457         * Updates the enrollment form fields to not use the group enrollment option.
458         */
459        function updateFormForNonGroupEnrollment() {
460            // Revert label of student email address field
461            $('label[for="id_email"]').html('Student\'s Email Address');
462
463            // Revert help text on the right hand side of step 2
464            $('#why-do-i-need-an-account').html(originalWhyDoINeedAnAccountText);
465
466            // Change label of contact info step (3) personal info portion
467            $('#enrollment_contact-info_personal-info-header').html('Student\'s Information');
468
469            // Change label of school question
470            $('label[for=id_name_of_school_attending]').html('Name of School Attending');
471
472            // Re-add T-shirt size option
473            $('#id_t_shirt_size').parent('.form-group').css('display', 'block');
474            $('#id_t_shirt_size').parent('.form-group').addClass('required');
475            $('#id_t_shirt_size').prop('required', 'required');
476
477            // // Change dropdown options for year of birth
478            // $('#id_birthdate_year').html(originalOptions);
479
480            // Hide is_chaperone checkbox (and un-check is_chaperone field)
481            $('#is_chaperone_checkbox_wrapper').hide();
482            $('input[name="is_chaperone"]').prop('checked', false);
483
484            // Change emergency contact back to parent
485            $('#primary-parent-info').html('Primary Parent\'s Information');
486            $('label[for=id_parent_email]').html('Parent\'s Email Address');
487            $('label[for=id_add_second_parent]').html('Add a second parent?');
488            $('#secondary-parent-info').html('Secondary Parent\'s Information');
489            $('label[for=id_secondary_parent_email]').html('Parent\'s Email Address');
490            $('.parent-email-different-text').html(
491                'The parent\'s email address must be different from the'
492                + ' student\'s email address. Both must be valid since you will'
493                + ' receive important communications related to your program'
494                + ' at this address.'
495            );
496
497            // Change the student address label
498            $('#student-address-header').html('Student\'s Address');
499
500            // Show secondary parent address fields if checked
501            hide_or_show_second_parent_form();
502
503            // // Take off event handler that removes the primary parent address if student over 18
504            // $('#id_birthdate_month').off('change');
505            // $('#id_birthdate_day').off('change');
506            // $('#id_birthdate_year').off('change');
507
508            // Show parent address
509            $('#primary-parent-address').css('display', 'block');
510            $('#primary-parent-address .form-group').addClass('required');
511            $('#primary-parent-address .form-group :input').prop('required', true);
512
513            // Show parent information fields for update
514            $('.parent-emergency-contact-fields').css('display', 'block');
515            $('.parent-emergency-contact-fields .form-group').addClass('required');
516            $('.parent-emergency-contact-fields .form-group :input').prop('required', true);
517
518            // If no parent, then show parent address fields
519            if (typeof no_parent != 'undefined' && no_parent == 'True') {
520                $('.parent-emergency-contact-address-fields').css('display', 'block');
521                $('.parent-emergency-contact-address-fields .form-group').addClass('required');
522                $('.parent-emergency-contact-address-fields .form-group :input').prop('required', true);
523            }
524        }
525
526        // Displays the parent address if the birthday passed in is under 18 years old
527        function renderParentAddressDependingOnBday(birthday) {
528            if (((new Date().getTime() - birthday.getTime()) / (1000 * 60 * 60 * 24 * 365)) >= 18) {
529                $('#primary-parent-address').css('display', 'none');
530                $('#primary-parent-address .form-group').removeClass('required');
531                $('#primary-parent-address .form-group :input').prop('required', false);
532            } else {
533                $('#primary-parent-address').css('display', 'block');
534                $('#primary-parent-address .form-group').addClass('required');
535                $('#primary-parent-address .form-group :input').prop('required', true);
536            }
537        }
538
539        function findProductById(targetId, jsonData) {
540            var sourceData = jsonData || window.backendPayload || window.backend || {};
541            var normalizedTargetId = String(targetId || '').trim();
542            var specialCategoryTypeMap = {
543                'addon_groupings': 'Add-On',
544                'fellowship_groupings': 'Fellowship',
545                'special_offers_groupings': 'Program',
546                'multi_program_groupings': 'Program'
547            };
548
549            function normalizeId(idValue) {
550                return String(idValue || '').trim();
551            }
552
553            function toProductResult(product, inferredType) {
554                if (!product) {
555                    return null;
556                }
557
558                return {
559                    'prodid': product.id || product.product_id,
560                    'summary': product.program_summary || product.name,
561                    'tuition': product.tuition,
562                    'duenow': product.due_upon_application,
563                    'name': product.name,
564                    'country': product.country,
565                    'startdate': product.start_date,
566                    'enddate': product.end_date,
567                    'type': inferredType || 'Program'
568                };
569            }
570
571            function findInProgramGroupings(groupings, defaultType) {
572                if (!Array.isArray(groupings)) {
573                    return null;
574                }
575
576                for (var i = 0; i < groupings.length; i++) {
577                    var grouping = groupings[i] || {};
578                    var years = grouping.years || [];
579
580                    for (var y = 0; y < years.length; y++) {
581                        var year = years[y] || {};
582                        var products = year.products || [];
583
584                        for (var p = 0; p < products.length; p++) {
585                            var product = products[p] || {};
586                            if (normalizeId(product.id) === normalizedTargetId) {
587                                return toProductResult(product, defaultType || 'Program');
588                            }
589                        }
590                    }
591                }
592
593                return null;
594            }
595
596            function findInDestinationProgramList(destinationProgramList) {
597                if (!Array.isArray(destinationProgramList)) {
598                    return null;
599                }
600
601                for (var i = 0; i < destinationProgramList.length; i++) {
602                    var destinationEntry = destinationProgramList[i] || {};
603
604                    var found = findInProgramGroupings(destinationEntry.individual_programs, 'Program');
605                    if (found) {
606                        return found;
607                    }
608
609                    found = findInProgramGroupings(destinationEntry.group_programs, 'Program');
610                    if (found) {
611                        return found;
612                    }
613                }
614
615                return null;
616            }
617
618            var yearsData = sourceData.data_by_year || {};
619            for (var yearKey in yearsData) {
620                if (!Object.prototype.hasOwnProperty.call(yearsData, yearKey)) {
621                    continue;
622                }
623
624                var yearPayload = yearsData[yearKey] || {};
625                var foundProduct = findInDestinationProgramList(yearPayload.destination_program_list);
626                if (foundProduct) {
627                    return foundProduct;
628                }
629            }
630
631            var specialCategories = sourceData.special_categories || {};
632            for (var categoryKey in specialCategories) {
633                if (!Object.prototype.hasOwnProperty.call(specialCategories, categoryKey)) {
634                    continue;
635                }
636
637                var foundSpecialProduct = findInProgramGroupings(
638                    specialCategories[categoryKey],
639                    specialCategoryTypeMap[categoryKey] || 'Program'
640                );
641                if (foundSpecialProduct) {
642                    return foundSpecialProduct;
643                }
644            }
645
646            // Last resort fallback for unexpected payload shape.
647            var fallbackResult = null;
648            function recursiveFallback(node) {
649                if (!node || typeof node !== 'object' || fallbackResult) {
650                    return;
651                }
652
653                if (Array.isArray(node)) {
654                    for (var i = 0; i < node.length; i++) {
655                        recursiveFallback(node[i]);
656                    }
657                    return;
658                }
659
660                if (
661                    normalizeId(node.id) === normalizedTargetId &&
662                    node.tuition !== undefined &&
663                    node.due_upon_application !== undefined
664                ) {
665                    fallbackResult = toProductResult(node, 'Program');
666                    return;
667                }
668
669                for (var key in node) {
670                    if (Object.prototype.hasOwnProperty.call(node, key)) {
671                        recursiveFallback(node[key]);
672                    }
673                }
674            }
675
676            recursiveFallback(sourceData);
677            return fallbackResult;
678        }
679
680        // Highlights any Regions or Destinations that have a picked program
681        function highlightPicked() {
682            $('.region, .location-container, tr.product-row, tr.product-name-grouping').removeClass('picked');
683
684            $.each(selected_products, function() {
685                var $trip = ensureProductRowLoaded(this);
686
687                if (!$trip.length) {
688                    return true;
689                }
690
691                $trip.addClass('picked');
692                $trip.closest('tr.product-name-grouping').addClass('picked');
693                var $destination = $('#' + $trip.data('containerid'));
694                $destination.addClass('picked').closest('.region').addClass('picked');
695            });
696        }
697
698        // Builds the table of line items and total fees due at the bottom of the page
699        function buildLineItems() {
700            var isGroup = false;
701            var isFuture = false;
702            var hideAppFee = true;
703            var primaryYear = window.backendPayload.primary_year.toString().trim();
704
705            if (selected_products.length > 0) {
706                $('.no-products-selected').hide();
707                $('.program-totals').show();
708                
709                $.each(selected_products, function() {
710                    var prod_id = this;
711                    var $t = $('#product-' + prod_id);
712                    if ($t.hasClass('group')) { isGroup = true; }
713                    
714                    // Check product year
715                    var productInfo = product_to_destination_list.filter(function(p) {
716                        return p.product_id == prod_id;
717                    })[0];
718
719                    if (productInfo && productInfo.year) {
720                        if (productInfo.year.toString().trim() !== primaryYear) {
721                            isFuture = true;
722                        }
723                    }
724                });
725            } else {
726                $('.program-totals').hide();
727                $('.no-products-selected').show();
728            }
729
730            $('.line-item').remove(); 
731
732            // Add Trip Line Items
733            $.each(selected_products, function() {
734                var prod_id = this;
735                var $trip = $('#product-' + prod_id);
736
737                var tripData = $trip.data() || {};
738                if (tripData.tuition === undefined || tripData.duenow === undefined) {
739                    tripData = findProductById(prod_id, window.backendPayload) || tripData;
740                }
741
742                if (tripData.type == 'Program') { hideAppFee = false; }
743                console.log('hideappfee? ', hideAppFee);
744
745
746                var $line_item = $line_item_template.clone();
747                $line_item.attr('id','#lineitem-' + prod_id);
748                $line_item.find('.summary').html(tripData.summary || $trip.data('summary') || '');
749                $line_item.find('.total').html(accounting.formatMoney(tripData.tuition || 0, "$", 2));
750                $line_item.find('.duenow').html(accounting.formatMoney(tripData.duenow || 0, "$", 2));
751                $line_item.find('.action').html('<a href="#product-' + (tripData.prodid || prod_id) + '" class="remove">remove</a>');
752                $line_item.appendTo($('tbody.trip-lineitems'));
753            });
754
755            // Add Fee Line Items
756            $.each(fees, function(index, fee) {
757                var $line_item = $line_item_template.clone();
758                var finalTotal, finalDue;
759                
760                if (index === 0 && isGroup) {
761                    finalTotal = fee.total_group;
762                    finalDue = fee.duenow_group;
763                } else if (index === 0 && isFuture) {
764                    finalTotal = fee.total_future;
765                    finalDue = fee.duenow_future;
766                } else {
767                    finalTotal = fee.total;
768                    finalDue = fee.duenow;
769                }
770
771                $line_item.attr('id','#lineitem-' + fee.id);
772                $line_item.find('.summary').html(fee.summary);
773                $line_item.find('.total').html(accounting.formatMoney(finalTotal, "$", 2));
774                $line_item.find('.duenow').html(accounting.formatMoney(finalDue, "$", 2));
775                $line_item.appendTo($('tbody.fee-lineitems'));
776            });
777
778            if (hideAppFee) { $('tbody.fee-lineitems').hide(); } 
779            else { $('tbody.fee-lineitems').show(); }
780        }
781
782        /**
783         * Updates the total prices displayed on the final application step of this page.
784         * Handles Standard, Group, and Future Year fee logic using direct data lookup.
785         */
786        function updateTotals() {
787            var total_tuition = 0;
788            var total_trips_duenow = 0;
789            var total_fees = 0;
790            var total_fees_duenow = 0;
791            var total_scholarships = 0;
792            var total_scholarships_due_now = 0;
793
794            var hideAppFee = true;
795            var isGroup = false;
796            var isFuture = false;
797            var primaryYear = window.backendPayload.primary_year.toString().trim();
798            var parsedDiscountPercent = parseFloat(applicationDiscountPercent || 0);
799            var hasDiscountPercent = !isNaN(parsedDiscountPercent) && parsedDiscountPercent > 0;
800
801            // 1. Analyze selected products to determine tuition and fee types
802            $.each(selected_products, function() {
803                var prod_id = this;
804                var $trip = $('#product-' + prod_id);
805                var tripData = $trip.data() || {};
806
807                if (tripData.tuition === undefined || tripData.duenow === undefined) {
808                    tripData = findProductById(prod_id, window.backendPayload) || tripData;
809                }
810                
811                // Sum Tuition (using || 0 in case the row isn't in the DOM yet)
812                total_tuition += parseFloat(tripData.tuition || 0);
813                total_trips_duenow += parseFloat(tripData.duenow || 0);
814
815                // Only show Application Fee if the product type is 'Program'
816                if (tripData.type === 'Program') {
817                    hideAppFee = false;
818                }
819
820                // Check for Group status
821                if ($trip.hasClass('group')) {
822                    isGroup = true;
823                }
824
825                // Check product year
826                var productInfo = product_to_destination_list.filter(function(p) {
827                    return p.product_id == prod_id;
828                })[0];
829
830                if (productInfo && productInfo.year) {
831                    var tripYear = productInfo.year.toString().trim();
832                    // If the trip year is different from the primary year, it's a Future fee
833                    if (tripYear !== primaryYear) {
834                        isFuture = true;
835                    }
836                }
837            });
838
839            // 2. Calculate Fees based on priority: Group > Future > Standard
840            $.each(fees, function(index, fee) {
841                if (hideAppFee) return false; // Break loop if no programs selected
842                
843                var feePrice = 0;
844
845                // Index 0 is typically the Application Fee
846                if (index === 0) {
847                    if (isGroup) {
848                        feePrice = fee.total_group;
849                    } else if (isFuture) {
850                        feePrice = fee.total_future;
851                    } else {
852                        feePrice = fee.total;
853                    }
854                } else {
855                    // Standard behavior for any secondary fees
856                    feePrice = fee.total;
857                }
858                
859                total_fees += parseFloat(feePrice || 0);
860                // Application fees are 100% due now
861                total_fees_duenow += parseFloat(feePrice || 0); 
862            });
863
864            // 3. Apply scholarship discounts 
865            if (hasDiscountPercent) {
866                var percentDiscountAmount = -1 * (total_tuition) * (parsedDiscountPercent / 100);
867                total_scholarships += percentDiscountAmount;
868                $('.scholarship-total').html(formatScholarshipMoney(percentDiscountAmount));
869            } else if (applicationScodeUnformattedPrice && applicationScodeUnformattedPrice.length > 0) {
870                var floatUnitPrice = parseFloat(applicationScodeUnformattedPrice);
871                if (!isNaN(floatUnitPrice) && floatUnitPrice !== 0) {
872                    if (floatUnitPrice > 0) floatUnitPrice = -floatUnitPrice;
873                    total_scholarships += floatUnitPrice;
874                }
875            }
876
877            if (applicationDiscountAppliesToDueNow && applicationScodeUnformattedDueNowPrice && applicationScodeUnformattedDueNowPrice.length > 0) {
878                var floatDueNowPrice = parseFloat(applicationScodeUnformattedDueNowPrice);
879                if (!isNaN(floatDueNowPrice) && floatDueNowPrice !== 0) {
880                    if (floatDueNowPrice > 0) floatDueNowPrice = -floatDueNowPrice;
881                    total_scholarships_due_now += floatDueNowPrice;
882                }
883            }
884
885            // 4. Final Grand Totals
886            var grand_total = total_fees + total_tuition + total_scholarships;
887            var grand_total_due_now = total_trips_duenow + total_fees_duenow + total_scholarships_due_now;
888
889            // Prevent negative displays
890            if (grand_total < 0) grand_total = 0;
891            if (grand_total_due_now < 0) grand_total_due_now = 0;
892
893            // 5. Update UI Labels
894            $('.grand-total').html(accounting.formatMoney(grand_total, "$", 2));
895            $('.total-duenow').html(accounting.formatMoney(grand_total_due_now, "$", 2));
896        }
897
898        // Show Secondary Parent's Information form if "Add a second parent?" checkbox is checked
899        function hide_or_show_second_parent_form() {
900            if ($('#id_add_second_parent').is(':checked')) {
901                if (selected_products.length <= 0 || !$('#product-' + selected_products[0]).hasClass('group')) {
902                    $('#second-parent-address').css('display', 'block');
903                    $('#second-parent-address .form-group').addClass('required');
904                    $('#second-parent-address .form-group :input').prop('required', true);
905                }
906                $('#second-parent-info').css('display', 'block');
907                $('#second-parent-info .form-group').addClass('required');
908                $('#second-parent-info .form-group :input').prop('required', true);
909            } else {
910                $('#second-parent-info').css('display', 'none');
911                $('#second-parent-address').css('display', 'none');
912                $('#second-parent-info .form-group').removeClass('required');
913                $('#second-parent-address .form-group').removeClass('required');
914                $('#second-parent-info .form-group :input').prop('required', false);
915                $('#second-parent-address .form-group :input').prop('required', false);
916            }
917        }
918        $('#id_add_second_parent').change(function () {
919            hide_or_show_second_parent_form();
920        });
921        $(document).ready(function () {
922            hide_or_show_second_parent_form();
923            if (selected_products.length > 0) showOrHideGroupFields();
924        });
925
926        // Show Advisor's Information form if "Add an advisor?" checkbox is checked
927        function hide_or_show_advisor_form() {
928            if ($('#id_add_advisor').is(':checked')) {
929                $('#advisor-info').css('display', 'block');
930                $('#advisor-info .form-group').addClass('required');
931                $('#advisor-info .form-group :input').prop('required', true);
932            } else {
933                $('#advisor-info').css('display', 'none');
934                $('#advisor-info .form-group').removeClass('required');
935                $('#advisor-info .form-group :input').prop('required', false);
936            }
937        }
938        // Show error message for privacy policy checkbox if unchecked
939        function hide_or_show_privacy_policy_error() {
940            if ($('#id_privacy_policy').is(':checked')) {
941                $('#privacy_policy_error').css('display', 'none');
942                $('#id_privacy_policy').closest('.form-group').removeClass('has-error');
943            } else {
944                $('#privacy_policy_error').css('display', 'block');
945                $('#id_privacy_policy').closest('.form-group').addClass('has-error');
946            }
947        }
948        $('#id_add_advisor').change(function () {
949            hide_or_show_advisor_form();
950        });
951        $('#id_privacy_policy').change(function () {
952            hide_or_show_privacy_policy_error();
953        });
954        $(document).ready(function () {
955            hide_or_show_advisor_form();
956        });
957
958        // Fill Parent Billing if "Same as Student" checkbox is checked
959        function set_parent_billing(checked_only) {
960            if ($('#id_parent_address_same_as_student').is(':checked')) {
961                $('#id_parent_mailing_street').val($('#id_student_mailing_street').val()).prop('readonly', true);
962                $('#id_parent_mailing_city').val($('#id_student_mailing_city').val()).prop('readonly', true);;
963                $('#id_parent_mailing_state').val($('#id_student_mailing_state').val()).prop('readonly', true);;
964                $('#id_parent_mailing_postal_code').val($('#id_student_mailing_postal_code').val()).prop('readonly', true);
965                $('#id_parent_mailing_country').val( $('#id_student_mailing_country option:selected').val() ).attr('readonly', true).css('pointer-events','none');
966            } else if ( !checked_only) {
967                //Clear on uncheck
968                $('#id_parent_mailing_street').val('').prop('readonly', false);
969                $('#id_parent_mailing_city').val('').prop('readonly', false);
970                $('#id_parent_mailing_state').val('').prop('readonly', false);
971                $('#id_parent_mailing_postal_code').val('').prop('readonly', false);
972                $('#id_parent_mailing_country').val('').attr('readonly', false).css('pointer-events','auto');
973            }
974
975            set_secondary_parent_billing(true);
976        }
977        $('#id_parent_address_same_as_student').change(function () {
978            set_parent_billing()
979        });
980        $('#id_student_mailing_street, #id_student_mailing_city, #id_student_mailing_state, #id_student_mailing_postal_code, #id_student_mailing_country').blur(function(){
981            set_parent_billing(true);
982        });
983
984        // Fill Secondary Parent Billing if "Same as Primary Parent" checkbox is checked
985        function set_secondary_parent_billing(checked_only) {
986            if ($('#id_secodary_parent_address_same_as_parent').is(':checked')) {
987                $('#id_secondary_parent_mailing_street').val($('#id_parent_mailing_street').val()).prop('readonly', true);
988                $('#id_secondary_parent_mailing_city').val($('#id_parent_mailing_city').val()).prop('readonly', true);;
989                $('#id_secondary_parent_mailing_state').val($('#id_parent_mailing_state').val()).prop('readonly', true);;
990                $('#id_secondary_parent_mailing_postal_code').val($('#id_parent_mailing_postal_code').val()).prop('readonly', true);
991                $('#id_secondary_parent_mailing_country').val( $('#id_parent_mailing_country option:selected').val() ).attr('readonly', true).css('pointer-events','none');
992            } else if ( !checked_only) {
993                //Clear on uncheck
994                $('#id_secondary_parent_mailing_street').val('').prop('readonly', false);
995                $('#id_secondary_parent_mailing_city').val('').prop('readonly', false);
996                $('#id_secondary_parent_mailing_state').val('').prop('readonly', false);
997                $('#id_secondary_parent_mailing_postal_code').val('').prop('readonly', false);
998                $('#id_secondary_parent_mailing_country').val('').attr('readonly', false).css('pointer-events','auto');
999            }
1000        }
1001        $('#id_secodary_parent_address_same_as_parent').change(function () {
1002            set_secondary_parent_billing()
1003        });
1004        $('#id_parent_mailing_street, #id_parent_mailing_city, #id_parent_mailing_state, #id_parent_mailing_postal_code, #id_parent_mailing_country').blur(function(){
1005            set_secondary_parent_billing(true);
1006        });
1007
1008        // Set email field with contact info the same as user email address, make readonly
1009        function set_student_email() {
1010            if ( $('#id_email').val() != '' ) {
1011                $('#id_student_email').val($('#id_email').val()).prop('readonly', true);
1012            }
1013        }
1014        $('#id_email').blur(function(){
1015            if ( $('#content-container').hasClass('enroll-page') ) {
1016                set_student_email()
1017            }
1018        });
1019
1020        /**
1021         * Set our Enroll form Fields before submitting
1022         */
1023        function set_enroll_fields() {
1024            if ( $('#content-container').hasClass('enroll-page') ) {
1025                set_parent_billing(true);
1026                set_student_email();
1027            }
1028        }
1029        // Call immediately ... just in case
1030        set_enroll_fields();
1031
1032        /**
1033         * Display our loading spinner while the form is submitting.
1034         */
1035        $('form').submit(function(){
1036            set_enroll_fields();
1037
1038            // Skip if there is a loading message already initiated
1039            if ( $('#loading-message').is(":visible") ) {
1040                return;
1041            } else {
1042                loadingStart('Processing your form, please wait...');
1043                return true;
1044            }
1045        });
1046
1047        /**
1048         * Save Progress - saves a student's work without form validation
1049         */
1050        $('p.save-progress button').click(function(e){
1051
1052            // It's possible to paste in an essay greater than the 3000 char max, which without validation will save as an empty string
1053            var exceeds_max = false;
1054            $('form.wizard-form textarea').each(function(){
1055                if ($(this).val().length > 3000) {
1056                    exceeds_max = true;
1057                    alert('A text box on this page exceeds the maximum 3000 character limit. Please check and save again.');
1058                    return false;
1059                }
1060            });
1061
1062            if ( !exceeds_max ){
1063                loadingStart('Saving');
1064                $('#id_save_skip_validation').val('true');
1065                $('form.wizard-form').attr('novalidate','novalidate'); // Prevent HTML5 validation
1066            }
1067
1068            $('form.wizard-form button[type="submit"]').click(); // Have to use click. .submit() doesn't work if a submit button is present
1069        });
1070
1071        /**
1072         * Portal Nav - Check if we are mobile size and turn the sidebar into mobile menu
1073         */
1074        function toggleDashMobileNav() {
1075            if( $(window).width() <= 767 ) {
1076                $('#toggle-dash-nav').attr('aria-expanded', 'false');
1077                $('#account-menu').addClass('nav collapse');
1078            } else {
1079                $('#toggle-dash-nav').attr('aria-expanded', 'true');
1080                $('#account-menu').removeClass('nav collapse');
1081            }
1082        }
1083        // Call our resizing function on window resize
1084        $(window).resize(function() {
1085            toggleDashMobileNav()
1086        });
1087
1088        // Call this function immediately, in-case we are already mobile size.
1089        toggleDashMobileNav();
1090
1091
1092        //---------------------------------------------------------------------------//
1093        // These functions below handle submitting our scholarship code forms,       //
1094        // this is to be done with JS because we cannot nest an HTML form in a form. //
1095        //---------------------------------------------------------------------------//
1096
1097        /**
1098         * Submit the top scholarship code form with enter key press.
1099         */
1100        $("#scholarship-code-top").on("keypress", function(e) {
1101            // Was the "enter" key pressed (keycode 13)?
1102            if(e.which == 13) {
1103                // Prevent default here, so the outer form will not high-jack this action.
1104                e.preventDefault();
1105                // Delegate to generic function
1106                submitScholarshipForm("top");
1107            }
1108        });
1109
1110        /**
1111         * Submit the bottom scholarship code form with button click
1112         */
1113        $("#scholarship-form-top-btn-submit").on("click", function(e){
1114            e.preventDefault();
1115            // Delegate to generic function
1116            submitScholarshipForm("top");
1117        });
1118
1119        /**
1120         * Submit the bottom scholarship code form with enter key press.
1121         */
1122        $("#scholarship-code-bottom").on("keypress", function(e) {
1123            // Was the "enter" key pressed (keycode 13)?
1124            if(e.which == 13) {
1125                // Prevent default here, so the outer form will not high-jack this action.
1126                e.preventDefault();
1127                // Delegate to generic function
1128                submitScholarshipForm("bottom");
1129            }
1130        });
1131
1132        /**
1133         * Submit the bottom scholarship code form with button click
1134         */
1135        $("#scholarship-form-bottom-btn-submit").on("click", function(e){
1136            e.preventDefault();
1137            // Delegate to generic function
1138            submitScholarshipForm("bottom");
1139        });
1140
1141        /**
1142         * Submits a scholarship form on the enrollment page.
1143         *
1144         * @param whichForm - Which scholarship form on the page we are submitting,
1145         *                    can be one of: ["top", "bottom"]
1146         */
1147        function submitScholarshipForm(whichForm) {
1148            loadingStart('Processing your form, please wait...');
1149
1150            var formEle = "";
1151            var scode = "";
1152
1153            var formTopEle = $("#scholarship-form-top");
1154            var formBottomEle = $("#scholarship-form-bottom");
1155
1156            var codeTopEle = $("#scholarship-code-top");
1157            var codeBottomEle = $("#scholarship-code-bottom");
1158
1159
1160            // Check which form was submitted.
1161            if (whichForm === "bottom") {
1162                formEle = formBottomEle;
1163                scode = codeBottomEle.val();
1164
1165                // Clear the input field
1166                codeBottomEle.val("");
1167            } else {
1168                formEle = formTopEle;
1169                scode = codeTopEle.val();
1170                // Clear the input field
1171                codeTopEle.val("");
1172            }
1173
1174            // Clear both forms success and errors
1175            clearScholarshipFormErrorAndSuccess(formTopEle);
1176            clearScholarshipFormErrorAndSuccess(formBottomEle);
1177
1178            // Submit our scholarship code (the server side code will save the code in the session if this is successful)
1179            $.ajax({
1180                url: (typeof MY_SCHOLARSHIP_TOKEN_URL !== "undefined" ? MY_SCHOLARSHIP_TOKEN_URL : ""),
1181                method: "POST",
1182                dataType: "json",
1183                data: {
1184                    scode: scode,
1185                    csrfmiddlewaretoken: (typeof MY_CSRF_TOKEN !== "undefined" ? MY_CSRF_TOKEN : "")
1186                },
1187                success: function(serverData) {
1188                    console.log(serverData);
1189                    if (serverData.error) {
1190                        displayScholarshipFormError(formEle, serverData.error);
1191                    } else if (serverData.success) {
1192                        // First remove a previous scholarship (if we had one)
1193                        removeScholarshipFromForm();
1194                        // Now apply the scholarship to the application.
1195                        //   Applying the scholarship to the form really only updates the display of prices
1196                        //   The actual discount is applied when the form is submitted and is done server side.
1197                        applyScholarshipToForm(serverData.data);
1198                        displayScholarshipFormSuccess(formEle, serverData.success);
1199                    }
1200                },
1201                error: function(jqXhr, textStatus, errorThrown) {
1202                    console.error("Error submitting scholarship code: " + errorThrown + " (" + textStatus + ")");
1203                    displayScholarshipFormError(formEle, "Unexpected error, please try again. If this error persists please contact us.");
1204                },
1205                complete: function() {
1206                    loadingStop();
1207                }
1208            })
1209        }
1210
1211        function clearScholarshipFormErrorAndSuccess(formEle) {
1212            var formGroupEle = formEle.children(".form-group").first();
1213            // Remove error styling
1214            formGroupEle.removeClass("has-error");
1215
1216            // Remove error and success message
1217            formGroupEle.children(".form-messages-block").remove();
1218        }
1219
1220        function displayScholarshipFormError(formEle, errorMessage) {
1221            var formGroupEle = formEle.children(".form-group").first();
1222
1223            // Add error styling
1224            formGroupEle.addClass("has-error");
1225
1226            // Display error message
1227            var errorMessageEle = $("<div class='form-messages-block alert alert-danger'></div>");
1228            errorMessageEle.text(errorMessage);
1229            formGroupEle.append(errorMessageEle);
1230        }
1231
1232        function displayScholarshipFormSuccess(formEle, successMessage) {
1233            var formGroupEle = formEle.children(".form-group").first();
1234
1235            // Display error message
1236            var successMessageEle = $("<div class='form-messages-block alert alert-success'></div>");
1237            successMessageEle.text(successMessage);
1238            formGroupEle.append(successMessageEle);
1239        }
1240
1241        function hideScholarshipAppliedFields() {
1242            $(".scholarship-applied-to-program").hide();
1243            $(".scholarship-applied").hide();
1244        }
1245
1246        function showScholarshipAppliedFields() {
1247            $(".scholarship-applied-to-program").show();
1248            $(".scholarship-applied").show();
1249        }
1250
1251        function applyScholarshipToForm(sprod) {
1252
1253            var hasUnitPrice = sprod.unit_price && sprod.unit_price != "$0.00";
1254
1255            if (sprod && hasUnitPrice) {
1256                var scholarshipUnitPriceUnformatted = sprod.unit_price_unformatted;
1257                var scholarshipUnitPriceFormatted = sprod.unit_price;
1258
1259                console.log('discount precent? ', sprod.discount_percent);
1260                if (sprod.discount_percent) {
1261                    applicationDiscountPercent = parseFloat(sprod.discount_percent);
1262                    console.log('discount precent? ', applicationDiscountPercent);
1263                    // Set Unit Price based on discount percent and total tuition
1264                    var totalTuition = 0;
1265                    $.each(selected_products, function() {
1266                        var prod_id = this;
1267                        var $trip = $('#product-' + prod_id);
1268                        totalTuition += parseFloat($trip.data('tuition') || 0);
1269                    });
1270                    scholarshipUnitPriceUnformatted = (-1 * totalTuition * (applicationDiscountPercent / 100)).toFixed(2);
1271                    var scholarshipDisplayAmount = parseFloat(scholarshipUnitPriceUnformatted || 0);
1272                    if (scholarshipDisplayAmount > 0) {
1273                        scholarshipDisplayAmount = -scholarshipDisplayAmount;
1274                    }
1275                    scholarshipUnitPriceFormatted = formatScholarshipMoney(scholarshipDisplayAmount);
1276                }
1277
1278                var discountAppliesToDueNow = sprod.is_due_now_scholarship;
1279
1280                // Set the visible discount on the product rows.
1281                $(".scholarship-applied-to-program-value").text(scholarshipUnitPriceFormatted);
1282
1283                var scholarshipLineItemParentEle = $(".scholarship-applied-lineitems").first();
1284
1285                // Set the visible "Scholarship" line item with the discount on step 4
1286                var lineItem = $('<tr class="scholarship-line-item scholarship"></tr>');
1287                lineItem.append($('<td class="scholarship-summary"></td>').text(sprod.name));
1288                lineItem.append($('<td class="scholarship-total"></td>').text(scholarshipUnitPriceFormatted));
1289                lineItem.append($('<td class="scholarship-duenow"></td>').text((discountAppliesToDueNow ? sprod.due_upon_application : "$0.00")));
1290                lineItem.append($('<td class="action"></td>'));
1291                scholarshipLineItemParentEle.append(lineItem);
1292
1293                // Set our globals for the discount as updateTotals() will use them.
1294                applicationScodeUnformattedPrice = scholarshipUnitPriceUnformatted;
1295                applicationScodeFormattedPrice = scholarshipUnitPriceFormatted;
1296                applicationDiscountAppliesToDueNow = discountAppliesToDueNow;
1297                applicationScodeUnformattedDueNowPrice = sprod.due_upon_application_unformatted;
1298                applicationScodeFormattedDueNowPrice = sprod.due_upon_application;
1299                
1300
1301                console.log('scholarship unitprice unformat? ', applicationScodeUnformattedPrice);
1302                console.log('scholarship unitprice format? ', applicationScodeFormattedPrice);
1303
1304                // Applies the scholarship to the totals (if at least one product has been selected)
1305                if (selected_products.length) {
1306                    updateTotals();
1307                }
1308                showScholarshipAppliedFields();
1309            }
1310        }
1311
1312        function removeScholarshipFromForm() {
1313            // Hide the UI elements we previously showed.
1314            hideScholarshipAppliedFields();
1315
1316            // Remove/Reset the UI elements that were added earlier
1317            $(".scholarship-applied-to-program-value").text("$0.00");
1318            $(".scholarship-applied-lineitems").children(".scholarship-line-item").remove();
1319
1320            // Reset the globals so their values are cleared
1321            applicationScodeUnformattedPrice = "";
1322            applicationScodeFormattedPrice = "";
1323            applicationDiscountAppliesToDueNow = false;
1324            applicationScodeUnformattedDueNowPrice = "";
1325            applicationScodeFormattedDueNowPrice = "";
1326            applicationDiscountPercent = 0;
1327
1328            // Finally update the prices to be correct
1329            if (selected_products.length) {
1330                updateTotals();
1331            }
1332        }
1333
1334        function addTableToDestination(destinationSlug, yearData, targetYear) {
1335            // Predict both possible IDs the Django template might have generated
1336            var perfectSuffix = destinationSlug + '-' + targetYear; 
1337            var fallbackSuffix = destinationSlug + '-'; 
1338            
1339            // Try to find the exact year match first (e.g. for future years)
1340            var suffix = perfectSuffix;
1341            var $menu = $('#menu-' + suffix);
1342            
1343            // If it doesn't exist, it must be a primary year card with a blank year!
1344            if ($menu.length === 0) {
1345                suffix = fallbackSuffix;
1346                $menu = $('#menu-' + suffix);
1347            }
1348
1349            var table = $menu.find('#destination-table-' + suffix);
1350
1351            if (!table[0] && $menu.length > 0) {
1352                // Call the newly exposed buildTable function!
1353                DestMenu.buildTable($menu, destinationSlug, yearData, suffix);
1354            }
1355        }
1356
1357        /** Show/Hide credit card fees in balance table **/
1358        $('.cc-more-btn').on('click', function() {
1359            $('.table-cc-fee').show();
1360            $('.cc-more-btn').hide();
1361            $('.cc-less-btn').show();
1362        });
1363        $('.cc-less-btn').on('click', function() {
1364            $('.table-cc-fee').hide();
1365            $('.cc-more-btn').show();
1366            $('.cc-less-btn').hide();
1367        });
1368
1369        // Insurance delete form
1370        $('#insurance-delete-form').on('submit', function(event) {
1371            if (!confirm("Are you sure you want to remove this item?")) {
1372                event.preventDefault();
1373                loadingStop();
1374            }
1375        });
1376    });
1377})(jQuery);

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.