PageSourceSearch

https://portal.uclaextension.edu/restricted/2026.1.19_2916/javascript/coreDelayedLoad.js

js uclaextension.edu collected 2026-09-24 08:46:48 UTC 48,888 bytes, 1,317 lines download raw bytes

1/* Utility functions */
2var util = {
3    navigate: function(href) {
4        document.location = href;
5        return true;
6    },
7
8   navigateEncoded: function(href) {
9       document.location = encodeURI(href);
10       return true;
11   },
12
13   print: function() {
14       window.print();
15       return false;
16   }
17}
18
19function browserSupportFormValidation() {
20    return Modernizr.formvalidation;
21}
22
23function gotoLink (linkValue) {
24    document.location=linkValue;
25    return true;
26}
27
28function scrollToDivContainer(divContainer) {
29    $('html,body').animate({scrollTop: divContainer.offset().top},'slow');
30}
31
32/* Form functions */
33function checkModifiedStatus(form) {
34    if (!form) {
35        return $("form").data('changed');
36    }
37    else {
38        return $(form).data('changed');
39    }
40}
41
42function showCancelConfirmationChangeContentAndButtonLabel(cancellationMessageDivId, method, form, okButtonLabel, cancelButtonLabel) {
43    $("#buttonConfirmationPromptOK").html(okButtonLabel);
44    $("#buttonConfirmationPromptCancel").html(cancelButtonLabel);
45    var cancellationMessage = $("#"+cancellationMessageDivId).html();
46    return showCancelConfirmation(cancellationMessage, method, form);
47}
48
49function parentSiteNaviation(navigationType, defaultUrl) {
50    if (typeof parentSite !== 'undefined' && parentSite !== 'null' && parentSite.length > 0) {
51        var parentSiteLink = $("a[data-destiny-parent-site='" + parentSite + "'][data-destiny-parent-site-link-type='" + navigationType + "']");
52        if (parentSiteLink.length > 0) {
53            $("#confirmationMessagePromptWrapper").html(parentSiteLink.first().html());
54            $("#buttonConfirmationPromptOK").on("click",function() {
55                util.navigate(parentSiteLink.first().attr('href'));
56            });
57            $("#confirmPromptModal").modal("show");            
58            return false;
59        }
60    } 
61
62    util.navigate(defaultUrl);
63    return false;
64}
65
66function showCancelConfirmation(cancellationMessage, method, form) {
67    $("#confirmationMessagePromptWrapper").html(cancellationMessage);
68    $("#buttonConfirmationPromptOK").on("click",function() {
69        submitMethodSkipValidation(method, form);
70    });
71    $("#confirmPromptModal").modal("show");
72}
73
74function submitMethodSkipValidationWithVariable(method, form, inputId, inputValue) {
75    if (!form) {
76        form = $("#"+inputId).closest("form").get(0);
77    }
78    $("#"+inputId).val(inputValue);
79    return submitMethodSkipValidation(method, form);
80}
81
82function submitMethodSkipValidation(method, form) {
83    var formJQ = $(form);
84    formJQ.find("input[name=method]").val(method);
85    clearFileInputs(form);//need to clear non uploaded files to avoid struts errors
86
87    formJQ.trigger("submit");
88    return false;
89}
90
91function submitMethodWithVariable(buttonId, method, formElement, inputId, inputValue) {
92    $("#"+inputId).val(inputValue);
93    return submitMethod(buttonId, method, formElement);
94}
95
96function submitMethodWithNamedVariable(buttonId, method, formElement, name, nameValue) {
97    $("input[name=" + name +"]").val(nameValue);
98    return submitMethod(buttonId, method, formElement);
99}
100
101function doPrint(method, form) {
102    form.target = "_blank";
103	
104    var formJQ = $(form);
105    formJQ.find("input[name=method]").val(method);
106    formJQ.trigger("submit");    
107    
108    form.target = "_self";
109    return false;
110}
111
112/**
113 * Calls submitMethod with the specified arguments.  If this calls returns true the method invokes the submit method
114 * on the specified formElement, otherwise it returns the return value.
115 *
116 * Buttons on forms with an onsubmit="return false" should call this method instead of submitMethod to allow the form
117 * to be submitted correctly.  Forms which include a text field with an onchange event making an ajax call need to use
118 * this approach to prevent a bug in iOS/Mac OS Safari
119 *
120 * @param buttonId
121 * @param method
122 * @param formElement
123 */
124function submitMethodJS(buttonId, method, formElement) {
125    var retVal = submitMethod(buttonId, method, formElement);
126    if (retVal) {
127        formElement.submit();
128    }
129    return retVal;
130}
131
132function submitMethod(buttonId, method, formElement) {
133    var button;
134    if (buttonId == null || buttonId.trim() == "") {
135      var trgt = this.event.target;
136      if (trgt != null && trgt.tagName == "BUTTON") {
137        button = $(trgt);
138      }
139    } else {
140      button = $("#" + buttonId);
141    }
142    var allPrimaryButtons = $(".btn-primary");
143    disableElementsWithTagging(allPrimaryButtons);
144
145
146    var formJQ;
147    if (formElement != null) {
148        formJQ = $(formElement);
149    } else {
150        formJQ = button.closest("form");
151    }
152
153
154
155    formJQ.find("input[name=method]").val(method);
156    formJQ.off( "submit.ds").on("submit.ds", function(event) {
157        disableElementsForSubmit(formElement);
158        if (typeof submitMethodSpinnerDisplay !== 'undefined' && submitMethodSpinnerDisplay) {
159
160            event.preventDefault();
161
162            $("#ajaxLoadingModal").one('shown.bs.modal', function () {
163                formJQ.off( "submit.ds");
164                formJQ.submit();
165            });
166            $("#ajaxLoadingModal").modal("show");
167        }
168    });
169
170
171
172    if (button.attr("type") == 'submit' && browserSupportFormValidation()) {
173        enableElementsWithTagging();
174        return true; //let the browser perform form validation
175    } else {
176        return checkForRequiredFields(button, formJQ.get(0), allPrimaryButtons);
177    }
178}
179
180function disableElementsWithTagging(elementsToDisable) {
181    elementsToDisable.filter(":not(:disabled)")
182        .attr("disabled", true)
183        .attr("data-lllee-submit-disable", 'true');
184}
185
186function enableElementsWithTagging() {
187    document.querySelectorAll('[data-lllee-submit-disable="true"]').forEach(element => {
188        element.disabled = false;
189        element.removeAttribute('data-lllee-submit-disable');
190    });
191}
192
193function submitAndDisableAllButtons(buttonId, method, formElement) {
194    disableSecondaryButtonElements();
195    submitMethod(buttonId, method, formElement);
196}
197
198function checkForRequiredFields(button, formElement, allPrimaryButtons) {
199    var fieldsMissingRequired = "<br/>";
200    var fieldMissing = false;
201    var focused = false;
202
203    button.closest('form').find('input[required], select[required], textarea[required]').each(function() {                
204        var value = $(this).val();
205        var parentDiv = $(this).parent();
206        var inputLabel = '';
207        var hasValue = true;
208        
209        if (value == null || value == "") {
210            hasValue = false;            
211            var inputLabelJQ = parentDiv.find("label");
212            if (inputLabelJQ.length == 0) {
213                inputLabel = $(this).closest("div.form-group").find("label").text();
214            } else{
215                inputLabel = inputLabelJQ.text();
216            }
217        } else if ($(this).is(":checkbox") && !$(this).is(":checked")) {
218            hasValue = false;
219            inputLabel = parentDiv.text();
220        }
221        
222        if (hasValue) {
223            parentDiv.removeClass("has-error");
224        } else {
225            inputLabel = inputLabel.replace("*", "");
226            if (inputLabel.length > 0) {
227                fieldsMissingRequired = fieldsMissingRequired + inputLabel + "<br/>";
228                parentDiv.addClass("has-error");
229                fieldMissing = true;
230                if (!focused) {
231                    $(this).trigger("focus");
232                    focused = true;
233                }
234            }
235        }
236    });
237    
238    if (fieldMissing) {
239        var buttonId = button.attr("id");        
240        $("#warningPopupMissingRequiredFieldsPopupMessage").html(fieldsMissingRequired);
241        showPopover(buttonId, "warningPopupMissingRequiredFields", "");
242        $('input, select, textarea').on("click", function(e) {
243            $("#" + buttonId).popover('dispose');
244        });
245        $('input, select, textarea').on("blur", function(e) {
246            $("#" + buttonId).popover('dispose');
247        });
248        enableElementsForSubmit(formElement);
249        return false;
250    } else {
251        clearFileInputs(formElement);//need to clear non uploaded files to avoid struts errors
252        if (button.attr("type") == 'submit') {
253            enableElementsWithTagging();
254            return true;    // if button is to submit form, let it submit
255        } else {
256            formElement.submit();
257            return false;
258        }        
259    }
260
261}
262
263function showPopover(buttonId, divId, placementLocation) {
264    var titleText = $("#"+divId).attr("title");
265    var contentText = $("#"+divId).html();
266    if (placementLocation == '') {
267        placementLocation = 'bottom';
268    }
269    
270    $("#"+buttonId).popover({title: titleText, content: contentText, placement: placementLocation, html: true, trigger: 'manual' });
271    $("#"+buttonId).popover('show');
272}
273
274function showWarningPopover(fieldId, errorPopupId, errorMsg) {
275    $("#"+errorPopupId).html(errorMsg);
276    $(".has-error").removeClass("has-error");
277    $("#"+fieldId).closest("div.form-group").addClass("has-error");    
278    showPopover(fieldId, errorPopupId, "bottom");
279    $('input').on("blur", function(e) {
280        $("#"+fieldId).popover('dispose');
281    });
282}
283
284function removeInvalidCharFromAttributeName(attrName) {
285    if(attrName) {
286        return attrName.replace(/([ #;&,.+*~\':"!^$[\]()=>|\/@])/g,'');
287    } else {
288        return attrName;
289    }
290}
291
292function escapeAllDataTarget() {
293  $('a[data-target]').each(function() {
294    var dataTargetId = $(this).attr("data-target");
295    var escapedDataTargetId = removeInvalidCharFromAttributeName(dataTargetId);
296    if(escapedDataTargetId != null && escapedDataTargetId != "") {
297      $(this).attr("data-target", "#" + escapedDataTargetId);
298      var targetDiv = document.getElementById(dataTargetId.substring(1));
299      if (targetDiv) {
300        targetDiv.id = escapedDataTargetId;
301      }
302    } else {
303      $(this).removeAttr("data-target");
304    }
305  });
306}
307
308function addRequiredSymbol() {
309    var showRequiredInstructions = false;
310    $('input[required], select[required], textarea[required], .requiredFieldset').each(
311            function() {
312                showRequiredInstructions = true;         
313                addRequiredSymbolToLabel($(this), false);   
314            }            
315        );
316    $('input.requiredNotValidated, select.requiredNotValidated, textarea.requiredNotValidated').each(
317            function() {
318                showRequiredInstructions = true;         
319                addRequiredSymbolToLabel($(this), false);   
320            }            
321        );
322    $('input.requiredConditionally, select.requiredConditionally, textarea.requiredConditionally').each(
323            function() {
324                showRequiredInstructions = true;                
325                addRequiredSymbolToLabel($(this), true);   
326            }            
327        );
328 
329    if (showRequiredInstructions) {
330        $("#requiredFieldInstruction,.requiredFieldInstructionAdditional").removeClass("d-none");
331    }
332    
333}
334
335/**
336 * Creates a JQuery ID selector from the specified ID, escaping any problematic characters
337 * @param myid
338 * @returns {string} a JQuery ID selector built from the specified ID (after escaping)
339 */
340function createEscapedIDSelector( myid ) {
341    if (!myid) {
342        return "";
343    }
344    return "#" + myid.replace( /(:|\.|\[|\]|\(|\))/g, "\\$1" );
345}
346
347function addRequiredSymbolToLabel(elementJQ, hideRequiredLabel) {
348    if (elementJQ.is('fieldset')) {
349        parentDiv = elementJQ;
350    } else if (elementJQ.is(':radio')) {
351        parentDiv = elementJQ.closest("div.radio");
352    } else if (elementJQ.is(':checkbox')) {
353        parentDiv = elementJQ.closest("div.checkbox");
354    } else {
355        parentDiv = elementJQ.closest("div.form-group");
356    }
357    
358    var label = parentDiv.find("label");
359    var symbolAdded = false;
360    
361    // for radio button, the required symbol should be added to the legend of the corresponding fieldset and it should only be added once per fieldset.
362    if (elementJQ.is(':radio')) {
363        var fieldset = parentDiv.closest("fieldset");
364        var legend = fieldset.find("legend").first();
365        if (legend.length > 0) {
366            if (legend.find("span.requiredFieldLabel").length == 0) {
367                label = legend;
368            } else {
369                symbolAdded = true;
370            }
371        }
371 else if (fieldset.find("span.requiredFieldLabel").length > 0) {
372            symbolAdded = true;
373        }
374    } else if (elementJQ.is("fieldset")) {
375        label = elementJQ.find("legend");
376    } else if (elementJQ.is(':checkbox')) {
377        if (parentDiv.find("span.requiredFieldLabel").length > 0) {
378            symbolAdded = true;
379        }
380    }
381    
382    if (!symbolAdded) {
383        if (label.find("span.labelContent").length === 0) {
384            var labelContent = label.html();
385            labelContent = "<span class='labelContent'>" + labelContent +"</span>";
386            label.html(labelContent);
387            if (label.is("legend") && label.parent("fieldset.accessibility").length > 0) {
388                // to prevent two colons from being displayed after the label (one before the required symbol and one after)
389                // add a CSS class to hide the colon displayed after the required symbol
390                label.addClass("noColonLegend");
391            }
392        }
393        var checkboxes = label.find("span.nicerCheckableCheckboxLabel");
394        if (checkboxes.length == 1){
395            checkboxes.addClass("requiredFieldLabel");
396            if (hideRequiredLabel)
397                checkboxes.addClass("noRequiredFieldLabel");
398        }
399        /* Only set the innermost span to have the requiredFieldLabel class */      
400        else if (checkboxes.length > 1){
401            for (var i = 0; i < checkboxes.length; i++){
402                var checkbox = $(checkboxes[i]);
403                if (checkbox.find("span.nicerCheckableCheckboxLabel").length == 0){
404                    checkbox.addClass("requiredFieldLabel");
405                    if (hideRequiredLabel)
406                        checkbox.addClass("noRequiredFieldLabel");
407                    break;
408                }
409            }
410        }
411        else if (elementJQ.is(':radio') || elementJQ.is(':checkbox')) {
412            label.append("<span class='requiredFieldLabel'/>");
413            if (hideRequiredLabel) {
414                label.find("span.requiredFieldLabel").addClass("noRequiredFieldLabel");
415            }
416        }
417        else {
418            label.addClass("requiredFieldLabel");
419            if (hideRequiredLabel) 
420                label.addClass("noRequiredFieldLabel");
421        }
422    }                                
423}
424
425/**
426 * Returns the selector with ID of fieldIdOrName if found.
427 * If not found, returns the selector with name of fieldIdOrName.
428 * If none found, or multiple found, empty selector will be returned.
429 * */
430function getIDOrNameSelector(fieldIdOrName) {
431    var fieldJQ = $(createEscapedIDSelector(fieldIdOrName));
432    if (fieldJQ.length === 1) {
433        return fieldJQ;
434    }
435
436    var fieldByNameJQ = $('[name="' + fieldIdOrName + '"]');
437    if (fieldByNameJQ.length === 1) {
438        return fieldByNameJQ;
439    }
440    return fieldJQ;
441}
442
443/*
444 * Applies Bootstrap 4 error message styling for the specified field and error message.
445 * If the field is not found, the error message is added to the default error panel
446 */
447function attachBootstrapFieldIsInvalidMessage(fieldIdOrName, errorMessage) {
448    var fieldJQ = getIDOrNameSelector(fieldIdOrName);
449    var errorMessageAppended = false;
450    if (fieldJQ.length > 0) {  // Field found
451        var fieldToMarkInvalidJQ = fieldJQ;
452        if (fieldToMarkInvalidJQ.attr("type") === "hidden") {
453            // find the closest visible input
454            fieldToMarkInvalidJQ = fieldToMarkInvalidJQ.prevAll("input[type!='hidden']:first");
455        }
456        if (fieldToMarkInvalidJQ.length > 0) {
457            fieldToMarkInvalidJQ.addClass('is-invalid');
458            fieldToMarkInvalidJQ.attr("aria-invalid", "true");
459            var invalidFeedbackHTML = "<div class='invalid-feedback w-100'>" + errorMessage + "</div>";
460            if (fieldJQ.is('fieldset')) {
461                fieldJQ.after(invalidFeedbackHTML);
462            } else {
463                var fieldParentJQ = fieldJQ.parent();
464                if (fieldJQ.siblings('.float-info-container').length > 0) {
465                    fieldJQ.addClass('form-control-with-icon');
466                }
467                fieldParentJQ.addClass("d-flex flex-wrap");
468                fieldParentJQ.append(invalidFeedbackHTML);
469            }
470            errorMessageAppended = true;
471        }
472    } else {  // Field not found
473        var preferredField = fieldIdOrName.replace(/(telephoneFax|email|address)ListArray\[(\d)\]\.(.*)/,"$1ListArray$2Preferred")
474        if (preferredField !== fieldIdOrName) {  // Tel/Email/Address special case
475            fieldJQ = $(createEscapedIDSelector(preferredField));
476            if (fieldJQ.length > 0 && fieldJQ.attr("type") !== "hidden") {
477                // append all error messages to the targetted element
478                if (!fieldJQ.hasClass('is-invalid')) {
479                    fieldJQ.addClass('is-invalid');
480                    fieldJQ.parent().append('<div class="invalid-feedback w-100"></div>');
481                }
482                var preferredFieldInvalidFeedbackJQ = fieldJQ.parent().find('div.invalid-feedback');
483                preferredFieldInvalidFeedbackJQ.append("<div>" + errorMessage + "</div>");
484                errorMessageAppended = true;
485            }
486        }
487    }
488
489    // If we were unable to append the message to the field, add to the default error panel.
490    if (errorMessageAppended ) {
491        if ($('#fieldValidationErrorsExistMessage').length === 0) {
492            $('#errorPanel ul').append("<li id='fieldValidationErrorsExistMessage'>" + fieldValidationErrorsExistMessage + "</li>");
493        }
494        appendErrorIconToScrollSpyMenuItems(fieldJQ);
495    } else {
496        $('#errorPanel ul').append("<li>" + errorMessage + "</li>")
497    }
498}
499
500/**
501 * Appends an error icon to the scroll spy menu item referencing the panel that this fieldJQ is housed in.
502 *
503 */
504function appendErrorIconToScrollSpyMenuItems(fieldJQ) {
505    if (fieldJQ.length > 0) {
506        var fieldSectionId = fieldJQ.closest("section").attr("id");
507        if (fieldSectionId.length > 0) {
508            var fieldSectionMenuItemJQ = $(".scrollSpyMenu").find("a.nav-link[href=\"#" + fieldSectionId + "\"]");
509            if (fieldSectionMenuItemJQ.length > 0 && fieldSectionMenuItemJQ.find(".scrollSpyMenuItemWarning").length === 0) {
510                fieldSectionMenuItemJQ.prepend(
511                    "<i class='fas fa-exclamation-circle fa-2x scrollSpyMenuItemWarning'></i>\n" +
512                    "<span class='sr-only'>" + scrollSpyMenuItemWarningSrOnlyMessage + "</span>");
513            }
514        }
515    }
516}
517
518/**
519 * Removes an error icon to the scroll spy menu item referencing the panel that this fieldJQ is housed in.
520 *
521 */
522function removeErrorIconFromScrollSpyMenuItems(fieldJQ) {
523    if (fieldJQ.length > 0) {
524        var fieldSectionJQ = fieldJQ.closest("section");
525        var fieldSectionId = fieldSectionJQ.attr("id");
526        if (fieldSectionId.length > 0 && fieldSectionJQ.find(".is-invalid").length === 0) {
527            var fieldSectionMenuItemJQ = $(".scrollSpyMenu").find("a.nav-link[href=\"#" + fieldSectionId + "\"]");
528            var scrollSpyMenuItemWarningJQ = fieldSectionMenuItemJQ.find(".scrollSpyMenuItemWarning");
529            if (fieldSectionMenuItemJQ.length > 0 && scrollSpyMenuItemWarningJQ.length > 0) {
530                fieldSectionMenuItemJQ.find(".scrollSpyMenuItemWarning").remove();
531            }
532        }
533    }
534}
535
536/*
537 * Applies Bootstrap 4 styling for the specified valid field and message.
538 */
539function attachBootstrapFieldIsValidMessage(fieldId, message) {
540    var fieldJQ = $(createEscapedIDSelector(fieldId));
541    if (fieldJQ.length > 0) {
542        fieldJQ.addClass('is-valid');
543        var validFeedbackHTML = "<div class='valid-feedback w-100'>" + message + "</div>";
544        if (fieldJQ.is('fieldset')) {
545            fieldJQ.after(validFeedbackHTML);
546        } else {
547            var fieldParentJQ = fieldJQ.parent();
548            if (fieldJQ.siblings('.float-info-container').length > 0) {
549                fieldJQ.addClass('form-control-with-icon');
550            }
551            fieldParentJQ.addClass("d-flex flex-wrap");
552            fieldParentJQ.append(validFeedbackHTML);
553        }
554    }
555    removeErrorIconFromScrollSpyMenuItems(fieldJQ);
556}
557
558/* Disable all clickable elements in form */
559function disableForm(form) {
560    var formVarJQ = $(form);
561    formVarJQ.find("input").attr("disabled", true);
562    formVarJQ.find("select").attr("disabled", true);
563    formVarJQ.find("button").attr("disabled", true);
564    formVarJQ.find("a").addClass("disabled");  // disable pretty checkbox and radio
565    formVarJQ.find('a[data-toggle]').prop("data-toggle", false); // disable popups
566}
567
568function addDisableLinkEvent() {
569    $('a[class=disabled]').on("click", function () {return false;});
570}
571
572function addDisableLinkAfterOneClickEvent(){
573    $('a.disabled-on-click').one('click',function(event){
574        //get the link clicked
575        var elem = $(this);
576        //disable it
577        elem.on("click", function(){
578            return false;
579        })
580    });
581}
582
583function setModifiedStatus(element) {
584    $(element).closest('form').data('changed', true);
585}
586
587/**
588 * Adds event handler to all input-type elements (input, button, textarea, select) inside a form
589 * for handling the change event. If any of the input-type elements are changed,
590 * the form is marked as changed. Used for determining if the lose unsaved changes confirmation
591 * dialog should be displayed.
592 * @param form [OPTIONAL] the target form element
593 */
594function trackFormChanged(form) {
595    if (form) {
596        $("#" + form.id + " :input").on("change", function () {
597            $(form).data('changed', true);
598        });
599    }
600    else {
601        $("form :input").on("change", function () {
602            $(this).closest('form').data('changed', true);
603        });
604    }
605}
606
607function popupURL(modalDivId, url, forceReload, postProcessCallbackMethodName, postProcessCallbackArgs, allowCache) {
608    if (modalDivId == '') {
609        return false;
610    }
611
612    if(!allowCache) {
613        allowCache = false;
614    }
615
616    var modalDiv = $('#'+modalDivId);
617    var contentLoaded = modalDiv.find("#" + modalDivId + "LoadingDiv").hasClass("d-none");
618    if(!contentLoaded || forceReload) {
619        modalDiv.find("#" + modalDivId + "LoadingDiv").removeClass("d-none");
620        modalDiv.find("#" + modalDivId + "ContentDiv").empty();
621    }
622    
623    modalDiv.modal('show');
624    
625    if (!contentLoaded || forceReload) {
626        $.ajax({
627            url: url,
628            type: "GET",            
629            dataType: "html",
630            cache: allowCache,
631            success: function( msg ) {
632                modalDiv.find("#" + modalDivId + "LoadingDiv").addClass("d-none");
633                var modalContentDiv = modalDiv.find("#" + modalDivId + "ContentDiv");
634                modalContentDiv.html(msg);
635                if (postProcessCallbackMethodName) {
636                    postProcessCallbackMethodName.apply(this, postProcessCallbackArgs);
637                }
638            },
639            error: function( msg ) {
640                modalDiv.find("#" + modalDivId + "LoadingDiv").addClass("d-none");
641                var modalContentDiv = modalDiv.find("#" + modalDivId + "ContentDiv");
642                var contentText = $('#ajaxError').html();
643                modalContentDiv.html(contentText);
644            }
645         });           
646    }
647    
648    return false;    
649}
650
651/**
652 * Opens a modal dialog loaded via an asynchronous HTTP (Ajax) call.  The dialog is removed from the DOM once it is closed.
653 * Prerequisites: The #ajaxLoadingModal dialog must be present on the page
654 *
655 * @param actionURL {string} the action URL method
656 * @param actionMethod {string} the action method that should be called
657 * @param {Object} settings the settings object which can specify the following properties
658 * @param {Element} settings.modalDialogTarget the HTML element to which the modal dialog should be appended.  If not specified the dialog is appended to the body
659 * @param {string} settings.linkId the ID of the element that was clicked to popup this modal dialog.  It is disabled while the modal loads
660 * @param {string} settings.modalId the HTML ID of the modal dialog
661 * @param {string} settings.httpMethod The HTTP method to use for the Ajax request (e.g. "POST", "GET", "PUT").  Defaults to GET when not specified
662 * @param {string} settings.dataType The type of data that you're expecting back from the server, e.g. "xml", "html", "json".  Defaults to "html"
663 * @param {string|PlainObject|Array} settings.data Data to be sent to the server. It is converted to a query string, if not already a string.
664 * It's appended to the url for GET-requests
665 *
666 * @see {@link https://api.jquery.com/jquery.ajax/|jQuery.ajax()}
667 * @returns {boolean} false
668 */
669function openAjaxModal(actionURL, actionMethod, settings) {
670    const requestType = settings.httpMethod || 'POST';
671    const dataType = settings.dataType || 'html';
672    const appendTargetElementJQ = settings.modalDialogTarget ? $(settings.modalDialogTarget) : $("body");
673    const data = settings.data;
674    const linkId = settings.linkId;
675    const linkJQ = $("#" + linkId);
676    const modalId = settings.modalId;
677    const ajaxLoadingModalJQ = $('#ajaxLoadingModal');
678    ajaxLoadingModalJQ.one('shown.bs.modal', function() {
679        $.ajax({
680            url : actionURL,
681            type : requestType,
682            data : data,
683            dataType : dataType,
684            cache : false,
685            success : function(msg) {
686                ajaxLoadingModalJQ.one('hidden.bs.modal', function() {
687                    appendTargetElementJQ.append(msg);
688                    const modalDialogJQ = $("#" + modalId);
689                    modalDialogJQ.modal('show');
690                    linkJQ.prop("disabled", false);
691
692                    // when modal is closed, remove the popup and destroy the modal
693                    modalDialogJQ.on('hidden.bs.modal', function() {
694                        $(this).data('modal', null);
695                        modalDialogJQ.remove();
696                    });
697                });
698                ajaxLoadingModalJQ.modal('hide');
699            },
700            error : function(jqXHR, textStatus, errorThrown) {
701                ajaxLoadingModalJQ.one('hidden.bs.modal', function() {
702                    linkJQ.prop("disabled", false);
703                    showPopover(linkId, "ajaxError", "");
704                });
705                ajaxLoadingModalJQ.modal('hide');
706            }
707        });
708        // disable button to prevent multiple submit
709        linkJQ.attr('disabled', 'disabled');
710    });
711    ajaxLoadingModalJQ.modal('show');
712
713    return false;
714}
715
716
717function popup(modalId){
718    if (modalId == null || modalId == '') {
719        return false;
720    }
721
722    $('#' + modalId).modal('show');
723    return false;
724}
725
726function fixYouTubeVideoZIndex(){
727    var paramToAdd = 'wmode=transparent';
728    $('iframe[src*="youtube.com"]').each(function(){
729        var url = $(this).attr('src');
730        var separator = '&';
731        if (!(url.indexOf('?') > -1)) separator = '?';
732        $(this).attr('src', url + separator + paramToAdd);
733    });
734}
735
736function loadEmbeddableLogin(button) {
737    var container = button.parent();
738    var href = container.attr("data-login-href");
739    var iframeContainer = $("#studentLoginFrameContainer");
740    if (iframeContainer.height() > 1) {
741        iframeContainer.empty();
742        button.children(".glyphicon-chevron-down").removeClass("glyphicon-chevron-down").addClass("glyphicon-chevron-left");
743        iframeContainer.animate({height: "0px"}, 500);
744        iframeContainer.hide();
745    } else {
746        if (href != "") {
747            iframeContainer.empty();
748            iframeContainer.append('<iframe id="studentLoginFrame" frameborder="0" src="'+href+'" width="100%" height="44px" scrolling="no"></iframe>');
749            button.children(".glyphicon-chevron-left").removeClass("glyphicon-chevron-left").addClass("glyphicon-chevron-down");
750            iframeContainer.show();
751            iframeContainer.animate({height: "44px"}, 500);
752        }
753    }
754}
755
756function togglePanelIcon(panel) {
757    $(panel)
758        .prev('.card-header')
759        .find("span.panelHeaderIcon")
760        .toggleClass('glyphicon-plus glyphicon-minus');
761
762    var panelIconElement = $(panel)
763        .prev('.card-header')
764        .find("span.panelHeaderIcon");
765    var panelCurrentlyExpanded = $(panel).prev('.card-header').attr('aria-expanded');
766    if(panelCurrentlyExpanded === "true") {
767        $(panelIconElement).attr('aria-pressed', 'false');
768    } else {
769        $(panelIconElement).attr('aria-pressed', 'true');
770    }
771}
772
773function togglePanelStyleClass(panel) {
774    $(panel).prev().parent().toggleClass('panel-default card-info');
775}
776
777function handlePanelCollapse(e) {
778    /* check for other implementations of handlePanelCollapse() in other JS when modifying this one*/
779    togglePanelIcon($(e.target));
780    togglePanelStyleClass($(e.target));
781}
782
783function initAccordions(){
784    initCollapsibles();
785}
786
787function initCollapsibles(){
788    $('.panel-default').on('hide.bs.collapse', handlePanelCollapse);
789    $('.panel-default').on('show.bs.collapse', handlePanelCollapse);
790    $('.card-info').on('hide.bs.collapse', handlePanelCollapse);
791    $('.card-info').on('show.bs.collapse', handlePanelCollapse);
792}
793
794function toggleAlreadyExpandedAccordion(section) {
795    if ($(section).find('.panel-collapse').hasClass('show')) {
796        togglePanelIcon($(section).find('.panel-collapse'));
797        $(section).find('.panel-collapse').each(function() {
798            togglePanelStyleClass($(this));
799        });
800        $(section).find('.card-header').toggleClass('collapsed')
801    }
802}
803
804function forceCloseBootstrapModal() {
805    $('body').removeClass('modal-open');
806    $('.modal-backdrop').remove();
807}
808
809function focusRequiredFieldInAccordian() {
810   /* Loop through each required input field and verify if the value is empty. If it is empty, focus on that accordian */
811   $(':input[required=""],:input[required]').each(function() {
812	     
813	    if($(this).is(":disabled")){
814	    	return true;
815	    }
816	   
817        //for radio buttons or checkboxes
818        if ($(this).is('input:radio') || $(this).is(':checkbox')) {
819            var name = $(this).attr("name");
820            var count = $("input[name ='"+name+"']:checked").length;
821            if(count == 0) {
822                showAccordianContainingObject($(this));
823                return false;
824            }
825        }
826
827        //for multi-selection
828        if ($(this).is('[multiple]')) {
829            var res = $(this).val();
830            if(res == null || res.length == 0) {
831                showAccordianContainingObject($(this));
832                return false;
833            }
834        }
835
836        //for all other input types including text, textarea, datepicker, etc.
837        else if ($(this).val().trim() == "") {
838            showAccordianContainingObject($(this));
839            return false;
840        }
841    });
842}
843
844function showAccordianContainingObject(currObject) {
845    //Need to manually collapse and expand panels because of a bug in bootstrap that breaks the accordian functionality
846    //if .collapse('hide') or .collapse('show') functions are used
847    
848    //collapse all panels
849    var allPanels = $(".panel-collapse.show");
850    togglePanelIcon(allPanels);
851    togglePanelStyleClass(allPanels);
852    allPanels.addClass("collapsed");
853    allPanels.attr("aria-expanded", false);
854    allPanels.removeClass("show");
855    
856    //Expand the panel of interest
857    var currPanel = currObject.closest(".panel-collapse");
858    togglePanelIcon(currPanel);
859    togglePanelStyleClass(currPanel);
860    currPanel.removeClass("collapsed");
861    currPanel.attr("aria-expanded", true);
862    currPanel.addClass("show").css("height","auto");
863
864    //show validation error if present.
865    if ($('#validationErrorMessage').length > 0) {
866        $('#validationErrorMessage').show();
867    }
868}
869    
870function isMobileView() {
871    return $(".accordionButtonContainer:visible").length > 0;
872}
873
874function clearFileInputs(form) {
875    if (form != null && form.enctype != "multipart/form-data") {
876        var formJQ = $(form);
877        var inputs = formJQ.find("input[type=file]");
878        for (var i = 0; i < inputs.length; i++) {
879            inputs[i].value = "";
880            inputs[i].disabled=true;
881        }
882    }
883}
884
885function enableFileInputs(form) {
886    var formJQ = $(form);
887    var inputs = formJQ.find("input[type=file]");
888	for (var i = 0; i < inputs.length; i++) {
889        inputs[i].value = "";
890        inputs[i].disabled=false;
891    }
892}
893
894function disableElementsForSubmit(formElement) {
895    var allPrimaryButtons = $(".btn-primary");
896    disableElementsWithTagging(allPrimaryButtons);
897    clearFileInputs(formElement);//need to clear non uploaded files to avoid struts errors
898}
899
900function enableElementsForSubmit(formElement) {
901    enableElementsWithTagging();
902    enableFileInputs(formElement);
903}
904
905function disableSecondaryButtonElements() {
906    var allSecondaryButtons = $(".btn-secondary");
907    if (allSecondaryButtons) {
908        allSecondaryButtons.attr("disabled", true);
909    }
910}
911
912/* Function will return null if no closest element found */
913function findClosestForm(element) {
914    var elementJQ = $(element);
915    var formJQ = elementJQ.closest("form");
916    if (formJQ.length > 0) {
917        return formJQ.get(0);
918    } else {
919        return null;
920    }
921}
922
923/* appends a unique page id for autotests */
924function appendTimestamp() {
925	$("#footer").append("<div id='timestamp' class='d-none'>"+Date.now()+"</div>");
926}
927
928function changeLanguage(locale, useSecureCookie) {
929    var now = new Date();
930    now.setFullYear(now.getFullYear() + 1);
931    if(useSecureCookie == 'true') {
932        document.cookie = "locale=" + locale + "; expires=" + now + "; path=/;" + "secure";
933    } else {
934        document.cookie = "locale=" + locale + "; expires=" + now + "; path=/;";
935    }
936
937    let isOverridingTargetUrl = false;
938    let overrideTargetFormElement = document.querySelector(".changeLocaleTargetAction");
939    if (overrideTargetFormElement) {
940        isOverridingTargetUrl = true;
941    }
942
943    let targetUrl = isOverridingTargetUrl ? overrideTargetFormElement.action : window.location.href;
944
945    var requestURL = "?method=changeLocale"
946    var params = parseUri(targetUrl).queryKey;
947    var keys = Object.keys(params);
948
949    for (var key in keys) {
950        if (keys[key] != 'method' && keys[key] != 'lang' ) {
951            requestURL += '&';
952            requestURL += keys[key];
953            requestURL += '=';
954            requestURL += params[keys[key]];
955        }
956    }
957
958    if (isOverridingTargetUrl) {
959        let pathOnly = parseUri(targetUrl).path;
960        window.location.href = "" + pathOnly + requestURL + "&lang=" + locale;
961    } else {
962        let appendHash = window.location.hash.split("#/").filter((item) => item).length > 0;
963        window.location.href = "" + requestURL + "&lang=" + locale + (appendHash ? window.location.hash : '');
964    }
965    return false;
966}
967
968/*Start of 3rd party functions */
969// parseUri 1.2.2
970// (c) Steven Levithan <stevenlevithan.com>
971// MIT License
972function parseUri (str) {
973    var	o   = parseUri.options,
974        m   = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
975        uri = {},
976        i   = 14;
977
978    while (i--) uri[o.key[i]] = m[i] || "";
979
980    uri[o.q.name] = {};
981    uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
982        if ($1) uri[o.q.name][$1] = $2;
983    });
984
985    return uri;
986};
987
988parseUri.options = {
989    strictMode: false,
990    key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
991    q:   {
992        name:   "queryKey",
993        parser: /(?:^|&)([^&=]*)=?([^&]*)/g
994    },
995    parser: {
996        strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
997        loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
998    }
999};
1000/* End of 3rd party functions */
1001
1002/* Ajax table functions.  This is to support the <srs:displayTable> tag. */
1003function doAjaxReplace(url, divId) {
1004    $("#"+divId).load(url + " #"+divId+"_div",
1005        function(response, status, xhr) {
1006            if(status == "error") {
1007                $("#"+divId).html(ajaxErrorMessage + " " + xhr.status + " " + xhr.statusText);
1008            } else {
1009                replaceAjaxTableLinks(divId);
1010            }
1011        }
1012    );    
1013}
1014
1015function replaceAjaxTableLinks(divId){
1016    $("div#"+divId+" table > thead > tr > th > a").each(
1017        function() {
1018        var linkStr = $(this).attr('href');
1019            if(!linkStr.match("^javascript:doAjaxReplace")) {
1020                $(this).attr("href", makeAjaxLink($(this).attr("href"), divId));
1021            }
1022        }
1023    );
1024    
1025    $("div#"+divId+" span.pagelinks a").each(
1026        function() {
1027        var linkStr = $(this).attr('href');
1028            if(!linkStr.match("^javascript:doAjaxReplace")) {
1029                $(this).attr("href", makeAjaxLink($(this).attr("href"), divId));
1030            }
1031        }
1032    );    
1033}
1034
1035function makeAjaxLink(href, divId) {
1036    return "javascript:doAjaxReplace('" + href + "&ajax=true&ajaxId="+divId+"&dt="+(new Date).getTime()+"', '"+divId+"');";
1037}
1038
1039function initAllAjaxTable() {
1040    $(".ajaxTableOuterWrapper").each(
1041        function() {
1042            replaceAjaxTableLinks(this.id);    
1043        }
1044    );
1045    
1046}
1047/* Ends Ajax table functions */
1048
1049function centeredPopup(page, popWidth, popHeight, close_only) {
1050
1051    var popF;
1052    if ( popF && !popF.closed && popF.close ) {
1053        popF.close();
1054    }
1055    if ( !close_only ) {
1056        if (popWidth==null) { popWidth=400 }
1057        if (popHeight==null) { popHeight=400 }
1058        winX=(screen.width-popWidth)/2
1059        winY=(screen.height-popHeight)/2
1060        popF = window.open(page, "Window", "width="+ popWidth + ",height=" + popHeight + ",resizable=yes,scrollbars=yes,toolbar=no,menubar=no,location=no,top="+winY+",left="+winX);
1061
1062    }
1063    return false;
1064}
1065
1066if (!String.prototype.startsWith) {
1067    String.prototype.startsWith = function(searchString, position){
1068        return this.substr(position || 0, searchString.length) === searchString;
1069    };
1070}
1071
1072function doCoreDelayedLoadSetup() {
1073    /* Add all document ready functions to callbacks so they can be re-called in the AJAX success handler */
1074    onAJAXSuccessCallbacks.add( escapeAllDataTarget );
1075    onAJAXSuccessCallbacks.add( addRequiredSymbol );
1076    onAJAXSuccessCallbacks.add( addDisableLinkEvent );
1077    onAJAXSuccessCallbacks.add( initDatePicker );
1078
1079    escapeAllDataTarget();
1080    addRequiredSymbol();
1081    addDisableLinkEvent();
1082    //did not put this last one in AJAX Success callbacks because it is currently only used on student login button which loads a new page on click
1083    addDisableLinkAfterOneClickEvent();
1084    disableInstructorClassListAfterOneClickEvent();
1085
1086    initAllAjaxTable();
1087    initDatePicker();
1088
1089    /* enable EmbeddableLogin on Click */
1090    $('#loginEmbeddableMenuButton').on("click", function() {
1091        loadEmbeddableLogin($(this));
1092    });
1093
1094    
1095    $(document).on("keyup", function(e) {
1096        if (e.keyCode == 27) { // escape key maps to keycode `27`
1097           $('.navbar .dropdown').removeClass('open')
1098           // close tooltips on escape for accessibility
1099           $('[data-toggle="tooltip"]').tooltip("hide");
1100       }
1101    });
1102
1103    /* Fix z-index for you-tube iframes */
1104    fixYouTubeVideoZIndex();
1105    
1106    applyAccessibilityMarkup();
1107}
1108
1109$(document).ready(function() {
1110    doCoreDelayedLoadSetup();
1111    // doCoreDelayedLoadSetup is also invoked by paymentPageDelayedLoad
1112    // Functions that attaches listeners to document should be placed here so that they will only be called once
1113    appendTimestamp();
1114    initAccordions();
1115    trapFocus();
1116});
1117
1118function trapFocus(){
1119    var tabSelectorTypes = "*[contenteditable], a[href], area[href], embed, *[tabindex], " + 
1120            "select, textarea, iframe, button, input, object";
1121    var tabSelectorGeneral = ":visible:not([disabled])";
1122    var previousFocused = null;
1123    var tabMode = false;
1124    var dialogue = null;
1125
1126    function findFocused(action){
1127        var focused = document.activeElement;
1128        // assume focused is visible and active
1129        if (focused && !$(focused).is(tabSelectorTypes)) focused = $(":focus")[0]; 
1130        if (focused &&  $(focused).is(tabSelectorTypes)) {
1131            if (action) action(focused);
1132            return focused;
1133        }
1134        return null; 
1135    };
1136
1137    $(document).on("keydown", function(e) {
1138        if (e.key !== "Tab") return;
1139        tabMode = true;
1140        if (dialogue === null) return;
1141        e.preventDefault();
1142        var focusPoints = $(tabSelectorTypes, dialogue).filter(tabSelectorGeneral);
1143        if (focusPoints.length === 0) {
1144            findFocused( function(item){ item.blur() });
1145            return;
1146        }
1147        var maxTab = focusPoints.length - 1;
1148        var focused = findFocused();
1149        // if for whatever reason the focus is not in focusPoints
1150        // index would return -1, and the following code will set the focus to
1151        // first item if it is tab
1152        // last item if it is shift tab
1153        var increment = (focused == null ? 0 : 1);
1154        var nextIndex = focusPoints.index(focused) + (e.shiftKey ? -1 : increment);
1155        if (nextIndex < 0) nextIndex = maxTab;
1156        if (nextIndex > maxTab) nextIndex = 0;
1157        focusPoints[nextIndex].focus();
1158    });
1159
1160    $(document).on("focusin", function(e) { 
1161        if (dialogue === null) return;
1162        var focusPoints = $(tabSelectorTypes, dialogue).filter(tabSelectorGeneral);
1163        if (focusPoints.length === 0) {
1164            findFocused( function(item){ item.blur() });
1165            return;
1166        }
1167        var focused = findFocused();
1168        // if for whatever reason the focus is not in focusPoints
1169        // index would return -1, and the following code will set the focus to first
1170        if (focusPoints.index(focused) < 0) focusPoints[0].focus();
1171    });
1172
1173    $(document).on("pointerdown", function(e) { tabMode = false; });
1174
1175    $(document).on("show.bs.modal", ".modal", function(e) {
1176        findFocused( function(item){ previousFocused = item });
1177    });
1178
1179    $(document).on("shown.bs.modal", ".modal", function(e) {
1180        dialogue = e.target;
1181        var focusPoints = $(tabSelectorTypes, dialogue).filter(tabSelectorGeneral);
1182        if (tabMode && focusPoints.length > 0){
1183            focusPoints[0].focus();
1184        } else {
1185            findFocused( function(item){ item.blur() });
1186        }
1187    });
1188
1189    $(document).on("hidden.bs.modal", ".modal", function(e) {
1190        dialogue = null;
1191        if (tabMode && previousFocused) previousFocused.focus();
1192    });
1193}
1194
1195function hideSkipToMainContainer(){
1196    $("#skip-to-main-container").removeClass('skipToMainContainerVisible');
1197    $("#skip-to-main").removeClass('skipToMainContentVisible').addClass('skipToMainContentHidden');
1198}
1199
1200function showSkipToMainContainer(){
1201    $("#skip-to-main").removeClass('skipToMainContentHidden').addClass('skipToMainContentVisible');
1202    $("#skip-to-main-container").addClass('skipToMainContainerVisible');
1203}
1204
1205
1206function clickElementById(elementId) {
1207    $(elementId).trigger("click");
1208    return false;
1209}
1210
1211
1212function refreshCartGlyphicon() {
1213    var requestURL = '/userSessionInfo.do';
1214    $.post(requestURL, {
1215        'method' : 'userSessionInfo'
1216        },
1217        function (data) {
1218          $("#cartCount").text(data.numCartItems);
1219          $("#headerCartCount").text(data.numCartItems + " " + $("#numItemsInCartMessage").val());
1220          var itemText = data.numCartItems == 1 ? $("#itemHelpText").attr("value") : $("#itemsHelpText").attr("value");
1221          var cartTitle = $("#numItemsInCartHelpText").attr("value") +
1222              " " + data.numCartItems +
1223              " " + (itemText != null ? itemText.toLowerCase() : "");
1224          $("#shoppingCartLink").attr("title", cartTitle).attr("aria-label", cartTitle);
1225        },
1226        "json"
1227    );
1228}
1229
1230function disableInstructorClassListAfterOneClickEvent(){
1231    $('a[id=instructorClassList]').one('click',function(event){
1232        //get the link clicked
1233        var elem = $(this);
1234
1235        elem.on("click", function(){
1236            checkModifiedStatus();
1237            //disable it
1238            return false;
1239        })
1240    });
1241}
1242
1243function submitProfileTemplatePreviewSettings(actionUrl, inputContainerId, completionRedirect) {
1244    var dataString=$("#" + inputContainerId).find("input,select").serialize();
1245    var request =  $.ajax({
1246        type: "POST",
1247        url: actionUrl,
1248        data: dataString,
1249        dataType: "html",
1250        cache: false
1251    }).always(function() {
1252        gotoLink(completionRedirect);
1253    });
1254}
1255
1256function onChangePreviewProgramOffice() {
1257    $("#profileTemplatePreviewCU option").addClass("hidden");
1258    $("#profileTemplatePreviewCU option:first").removeClass("hidden");
1259    $("#profileTemplatePreviewCU").val("");
1260
1261    var selectedPO = $("#profileTemplatePreviewPO").val();
1262    if (selectedPO !== '') {
1263        if ($("#profileTemplatePreviewCU option[data-parentId='" + selectedPO + "']").length > 0) {
1264            $("#profileTemplatePreviewCU option[data-parentId='" + selectedPO + "']").removeClass("hidden");
1265            $("#profileTemplatePreviewCU option[data-parentId='empty']").addClass("hidden");
1266            $("#profileTemplatePreviewCU").val($("#profileTemplatePreviewCU option[data-parentId='" + selectedPO + "']:first").val());
1267        } else {
1268            $("#profileTemplatePreviewCU option[data-parentId='empty']").removeClass("hidden");
1269        }
1270    }
1271}
1272
1273function accessibleTooltip(targetElementList, tooltipParamObj) {
1274
1275    let accessibleTooltipParams = { trigger: "manual" };
1276    if (tooltipParamObj) {
1277        tooltipParamObj = {...tooltipParamObj, ...accessibleTooltipParams};
1278    } else {
1279        tooltipParamObj = accessibleTooltipParams;
1280    }
1281
1282    targetElementList
1283        .attr("tabindex", 0)
1284        .tooltip(tooltipParamObj)
1285        .mouseover(event => {
1286            $(event.target).tooltip("show");
1287            $(".tooltip").on("mouseleave", function() {
1288                $(event.target).tooltip("hide");
1289            });
1290        })
1291        .mouseout(event => {
1292            setTimeout(() => {
1293                if (!$(".tooltip:hover").length) {
1294                    $(event.target).tooltip("hide");
1295                }
1296            }, 100);
1297        })
1298        .focus(event => {
1299            $(event.target).tooltip("show");
1300        })
1301        .blur(event => {
1302            $(event.target).tooltip("hide");
1303        });
1304}
1305
1306window.addEventListener('pageshow', function(event) {
1307    // if navigating back using browser-back, re-enable any elements disabled for the submit
1308    let restoreDisableState = event.persisted;
1309    if (performance && performance.getEntriesByType('navigation')[0]) {
1310        const navEntry = performance.getEntriesByType('navigation')[0];
1311        restoreDisableState = restoreDisableState || (navEntry.type === 'back_forward');
1312    }
1313    if (restoreDisableState) {
1314        enableElementsWithTagging();
1315    }
1316});
1317

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.