(function ($) {
'use strict';
var LISTS = {
colleges: [
"College of Agriculture",
"College of Education",
"College of Engineering",
"Exploratory Studies",
"College of Health & Human Sciences",
"College of Liberal Arts",
"Krannert School of Management",
"College of Pharmacy",
"Purdue Polytechnic Institute",
"College of Science",
"College of Veterinary Medicine"
],
schools: [
"Not Listed",
"Department of Agricultural and Biological Engineering",
"Department of Agricultural Economics",
"Department of Agricultural Sciences Education and Communication",
"Department of Agronomy",
"Department of Animal Sciences",
"Department of Biochemistry",
"Department of Botany and Plant Pathology",
"Department of Entomology",
"Department of Food Science",
"Department of Forestry and Natural Resources",
"Department of Horticulture and Landscape Architecture",
"Natural Resources and Environmental Science",
"Pre-Veterinary Medicine",
"Department of Curriculum and Instruction",
"Department of Educational Studies",
"College of Engineering Administration",
"Davidson School of Chemical Engineering",
"Division of Construction Engineering and Management",
"Division of Environmental and Ecological Engineering",
"Elmore Family School of Electrical and Computer Engineering",
"Lyles School of Civil Engineering",
"School of Aeronautics and Astronautics",
"School of Engineering Education",
"Edwardson School of Industrial Engineering",
"School of Materials Engineering",
"School of Mechanical Engineering",
"School of Nuclear Engineering",
"Weldon School of Biomedical Engineering",
"Consumer Science",
"Health and Kinesiology",
"Health Sciences",
"Hospitality and Tourism Management",
"Human Development and Family Sciences",
"Nursing",
"Nutrition Science",
"Psychological Sciences",
"Public Health",
"Speech, Language, and Hearing Sciences",
"Brian Lamb School of Communication",
"Department of Anthropology",
"Department of English",
"Department of History",
"Department of Philosophy",
"Department of Political Science",
"Department of Sociology",
"Patti and Rusty Rueff School of Design, Art, and Performance",
"School of Interdisciplinary Studies",
"School of Languages and Cultures",
"Department of Industrial and Physical Pharmacy",
"Department of Medicinal Chemistry and Molecular Pharmacology",
"Department of Pharmacy Practice",
"Biological Sciences",
"Chemistry",
"Computer Science",
"Earth, Atmospheric and Planetary Sciences",
"Mathematics",
"Physics and Astronomy",
"Science Administration",
"Statistics",
"Department of Basic Medical Sciences",
"Department of Comparative Pathobiology",
"Department of Veterinary Clinical Sciences",
"Department of Business Administration",
"Department of Exploratory Studies",
"John Martinson Honors College",
"Department of Information Studies",
"Department of Computer and Information Technology",
"Department of Computer Graphics Technology",
"Department of Technology Leadership and Innovation",
"Division of Military Science and Technology",
"School of Aviation and Transportation Technology",
"School of Construction Management Technology",
"School of Engineering Technology"
],
majors: [
"Accounting",
"Acting",
"Actuarial Science",
"Aeronautical and Astronautical Engineering",
"Aeronautical Engineering Technology",
"Aerospace Financial Analysis",
"African American Studies",
"Agribusiness (multiple concentrations)",
"Agricultural Communication",
"Agricultural Economics (multiple concentrations)",
"Agricultural Education",
"Agricultural Engineering",
"Agricultural Systems Management",
"Agronomy (multiple concentrations)",
"Airline Management and Operations",
"Airport Management and Operations",
"American Studies",
"Animal Sciences (multiple concentrations)",
"Animation",
"Anthropology",
"Applied Exercise and Health (Pre)",
"Applied Meteorology and Climatology",
"Aquatic Sciences",
"Art History",
"Asian Studies",
"Atmospheric Science/Meteorology",
"Audio Engineering Technology",
"Automation and Systems Integration Engineering Technology",
"Aviation Management",
"Biochemistry",
"Biochemistry (Biology)",
"Biochemistry (Chemistry)",
"Biological Engineering - multiple concentrations",
"Biological Engineering - multiple concentrations",
"Biology",
"Biomedical Engineering",
"Brain and Behavioral Sciences",
"Building Information Modeling",
"Cell Molecular and Developmental Biology",
"Chemical Engineering",
"Chemistry",
"Chemistry - American Chemical Society",
"Chinese Studies",
"Civil Engineering",
"Classical Studies",
"Communication General (Pre)",
"Comparative Literature",
"Computer and Information Technology",
"Computer Engineering",
"Computer Engineering Technology",
"Computer Science",
"Construction Engineering",
"Construction Management Technology",
"Creative Writing",
"Crop Science",
"Cybersecurity",
"Data Science",
"Data Visualization",
"Developmental and Family Science",
"Early Childhood Education and Exceptional Needs",
"Ecology Evolution and Environmental Sciences",
"Economics (Pre) (College of Liberal Arts)",
"Economics (School of Management)",
"Electrical Engineering",
"Electrical Engineering Technology",
"Elementary Education",
"Energy Engineering Technology",
"Engineering (First Year)",
"Engineering / Technology Teacher Education",
"English",
"Environmental and Ecological Engineering",
"Environmental and Natural Resources Engineering",
"Environmental and Natural Resources Engineering",
"Environmental Geosciences",
"Environmental Health Sciences",
"Environmental Studies (Pre)",
"Exploratory Studies (for undecided students)",
"Family and Consumer Sciences Education",
"Farm Management",
"Film and Theatre Production",
"Film and Video Studies",
"Finance",
"Financial Counseling and Planning",
"Flight (Professional Flight Technology)",
"Food Science",
"Forestry",
"French",
"Game Development and Design",
"General Education: Curriculum and Instruction (non-licensure)",
"General Education: Educational Studies (non-licensure)",
"Genetics",
"Geology and Geophysics",
"German",
"Global Studies",
"Health and Disease",
"Health Science PreProfessional",
"History",
"Horticulture (multiple concentrations)",
"Hospitality and Tourism Management",
"Human Resource Development",
"Human Services",
"Industrial Design",
"Industrial Engineering",
"Industrial Engineering Technology",
"Industrial Management",
"Insect Biology",
"Integrated Studio Arts BFA (Portfolio Required)",
"Interdisciplinary Engineering Studies",
"Interior Design - Professional Program",
"Italian Studies",
"Japanese",
"Jewish Studies",
"Kinesiology",
"Landscape Architecture (Pre)",
"Law and Society (Criminology)",
"Linguistics",
"Management (General)",
"Marketing",
"Materials Engineering",
"Mathematics",
"Mathematics - Business",
"Mathematics - Computer Science",
"Mathematics Education",
"Mathematics - Statistics",
"Mechanical Engineering",
"Mechanical Engineering Technology",
"Mechatronics Engineering Technology",
"Medical Laboratory Sciences",
"Microbiology",
"Motorsports Engineering",
"Multidisciplinary Engineering",
"Natural Resources and Environmental Science (multiple concentrations)",
"Network Engineering Technology",
"Neurobiology and Physiology",
"Nuclear Engineering",
"Nursing",
"Nutrition and Dietetics",
"Nutrition and Dietetics/Nutrition Fitness and Health (double major)",
"Nutrition Fitness and Health",
"Nutrition Science",
"Occupational Health Science",
"Organizational Leadership",
"Pharmacy",
"Philosophy",
"Physics",
"Planetary Sciences",
"Plant Genetics Breeding and Biotechnology",
"Plant Science",
"Plant Studies - Exploratory (Pre)",
"Political Science",
"Pre-dentistry",
"Pre-law",
"Pre-medicine",
"Pre-occupational Therapy",
"Pre-physical Therapy",
"Pre-physician Assistant",
"Pre-veterinary Medicine",
"Professional Writing",
"Psychological Sciences",
"Public Health",
"Purdue Polytechnic Institute Statewide Programs",
"Radiological Health Sciences",
"Religious Studies",
"Retail Management",
"Robotics Engineering Technology",
"Russian",
"Sales and Marketing",
"Science Education (Biology Chemistry Earth/Space Physics)",
"Selling and Sales Management",
"Social Studies Education",
"Sociology",
"Soil and Water Sciences",
"Sound for the Performing Arts",
"Spanish",
"Special Education: Dual Licensure in Elementary Education and Special Education - Mild Intervention",
"Special Education: Mild and Intense Intervention P-12",
"Special Education: Mild Intervention P-12",
"Speech Language and Hearing Sciences",
"Statistics - Applied Statistics",
"Statistics with Mathematics Option",
"Studio Arts and Technology",
"Supply Chain & Sales Engineering Technology",
"Supply Chain Information and Analytics",
"Sustainable Food and Farming Systems",
"Systems Analysis and Design",
"Theatre",
"Theatre Design and Production",
"Turf Management and Science",
"Undecided Liberal Arts",
"Unmanned Aerial Systems",
"UX Design",
"Veterinary Nurse (Formerly Veterinary Technology)",
"Virtual Product Integration",
"Visual Arts Design Education",
"Visual Arts Education",
"Visual Communication Design (Graphic Design)",
"Visual Effects Compositing",
"Web Programming and Design",
"Wildlife",
"Women's Gender and Sexuality Studies"
]
};
var FIELDS = [
{ list: 'colleges', selector: '#application-form input[name="department"]' },
{ list: 'colleges', selector: '.user-form.research input[name="college"]' },
{ list: 'schools', selector: '.user-form.research input[name="school"]' },
{ list: 'schools', selector: '.user-form.research input[name="school_secondary"]' },
{ list: 'majors', selector: '#find_student_major', picker: true },
{ list: 'majors', selector: '#application-form input[name="major"]' },
{ list: 'majors', selector: '#application-form input[name="previous_mentor_institution"]' },
{ list: 'majors', selector: '#application-form input[name="potential_major_1"]' },
{ list: 'majors', selector: '#application-form input[name="potential_major_2"]' },
{ list: 'majors', selector: '#application-form input[name="potential_major_3"]' }
];
var NO_RESTRICTION = 'No Major Restriction';
var DEFAULT_ASSISTIVE_HINT = 'When autocomplete results are available use up and down arrows to review and enter to select. Touch device users, explore by touch or with swipe gestures.';
function announce(message) {
if (typeof window.announceA11yStatus === 'function') {
window.announceA11yStatus(message);
}
}
function warn(message, el) {
if (window.console && window.console.warn) { window.console.warn('autocomplete_surf: ' + message, el); }
}
function isStrict(input) {
return input.getAttribute('data-autocomplete-mode') === 'strict';
}
function escapeHtml(text) {
return (text == null ? '' : String(text))
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"');
}
// Canonical list value matching text (trimmed, case-insensitive), or null.
function findChoice(choices, text) {
var needle = $.trim(text || '').toLowerCase();
if (!needle) { return null; }
for (var i = 0; i < choices.length; i++) {
if (choices[i].toLowerCase() === needle) { return choices[i]; }
}
return null;
}
// Exact matches first, then prefix matches, then substring matches (so autoselect picks well).
function rankedSource(choices) {
return function (query, populateResults) {
var needle = $.trim(query).toLowerCase();
var exact = [], prefix = [], contains = [];
for (var i = 0; i < choices.length; i++) {
var candidate = choices[i].toLowerCase();
var pos = candidate.indexOf(needle);
if (pos === -1) { continue; }
if (candidate === needle) { exact.push(choices[i]); }
else if (pos === 0) { prefix.push(choices[i]); }
else { contains.push(choices[i]); }
}
populateResults(exact.concat(prefix, contains));
};
}
// Text of the elements the original input referenced via aria-describedby.
function describedByText(input) {
var ids = $.trim(input.getAttribute('aria-describedby') || '').split(' ');
var parts = [];
for (var i = 0; i < ids.length; i++) {
var el = ids[i] ? document.getElementById(ids[i]) : null;
if (el) { parts.push($.trim($(el).text())); }
}
return parts.join(' ');
}
// Replaces a server-rendered with the accessible-autocomplete widget,
// preserving id (label association), name (submission), value, help text,
// aria-required and width class.
function enhanceInput(input, choices, extraOptions) {
if (!input.id) {
warn('input has no id, so its label cannot be preserved; left unenhanced', input);
return null;
}
var inputId = input.id;
var ariaRequired = input.getAttribute('aria-required');
var helpText = describedByText(input);
var container = document.createElement('div');
container.id = inputId + '-container';
container.className = 'surf-autocomplete' + ($(input).hasClass('w-90') ? ' w-90' : '');
// The widget has its own live region; stop an aria-live ancestor from also announcing the menu.
container.setAttribute('aria-live', 'off');
input.parentNode.insertBefore(container, input);
var options = $.extend({
element: container,
id: inputId,
name: input.name,
defaultValue: input.value || '',
source: rankedSource(choices),
minLength: 2,
displayMenu: 'overlay',
templates: { suggestion: escapeHtml },
tAssistiveHint: function () {
return helpText ? helpText + ' ' + DEFAULT_ASSISTIVE_HINT : DEFAULT_ASSISTIVE_HINT;
}
}, extraOptions || {});
input.parentNode.removeChild(input);
window.accessibleAutocomplete(options);
// The library has no aria-required option; set it on the rendered input.
var rendered = document.getElementById(inputId);
if (rendered && ariaRequired) { rendered.setAttribute('aria-required', ariaRequired); }
return container;
}
var strictFields = [];
// "Full match or nothing" for plain strict fields.
function initStrictChoice(container, inputId, choices, legacyValue) {
var label = document.querySelector('label[for="' + inputId + '"]');
var $error = $('');
if (label) { $(label).append($error); }
function field() { return document.getElementById(inputId); }
function showError(message) {
var el = field();
if (message) {
$error.text('Error: ' + message).prop('hidden', false);
if (el) { el.setAttribute('aria-invalid', 'true'); }
} else {
$error.text('').prop('hidden', true);
if (el) { el.removeAttribute('aria-invalid'); }
}
}
// Same unsupported-API workaround as the Majors picker.
function clearField(el) {
el.value = '';
el.dispatchEvent(new Event('input', { bubbles: true }));
}
// Returns true if the field holds an acceptable value; false if it was cleared.
function enforce() {
var el = field();
if (!el) { return true; }
var text = $.trim(el.value);
if (!text || (legacyValue && text === legacyValue) || findChoice(choices, text)) {
showError('');
return true;
}
clearField(el);
showError('"' + text + '" is not in the list and was removed. Choose from the suggestions.');
announce('"' + text + '" is not in the list and was removed.');
return false;
}
// Typing again clears a stale error. clearField also fires this, but it
// runs before showError sets the new message, so the order is safe.
container.addEventListener('input', function () { showError(''); });
// Focus moves into the menu options while arrowing, so only check once
// focus has left the whole widget.
container.addEventListener('focusout', function () {
window.setTimeout(function () {
if (!container.contains(document.activeElement)) { enforce(); }
}, 0);
});
container.addEventListener('keydown', function (event) {
var el = field();
if (!el || event.target !== el) { return; }
if (event.key !== 'Enter' && event.keyCode !== 13) { return; }
if (el.getAttribute('aria-expanded') === 'true') { return; }
if (!enforce()) { event.preventDefault(); }
}, true);
strictFields.push({ id: inputId, enforce: enforce });
}
// One submit check per form, bound directly on the form so it runs
// before handlers from scripts that load later.
function bindStrictSubmit() {
var forms = [];
$.each(strictFields, function (index, entry) {
var form = $('#' + entry.id).closest('form')[0];
if (form && $.inArray(form, forms) === -1) { forms.push(form); }
});
$(forms).on('submit', function (event) {
var form = this;
var firstCleared = null;
$.each(strictFields, function (index, entry) {
var el = document.getElementById(entry.id);
if (el && $.contains(form, el) && !entry.enforce() && !firstCleared) {
firstCleared = el;
}
});
if (firstCleared) {
event.preventDefault();
firstCleared.focus();
}
});
}
function initMajorPicker(input) {
var inputId = input.id;
var strict = isStrict(input);
var $button = $('#add-major-button');
var $list = $('#student_major_list');
var $noneItem = $('#no-major-restriction-item');
var $noneBox = $noneItem.find('input[type="checkbox"]');
if (!$button.length || !$list.length) { return; }
// The library owns the rendered input; re-query instead of caching the node.
function field() { return document.getElementById(inputId); }
function pendingValue() {
var el = field();
var text = el ? $.trim(el.value) : '';
return strict ? findChoice(LISTS.majors, text) : (text || null);
}
function updateButton() {
$button.attr('aria-disabled', pendingValue() ? 'false' : 'true');
}
// Unsupported-API workaround: the library tracks the value internally,
// so clearing needs an input event it will process. Test per browser.
function clearField() {
var el = field();
if (!el) { return; }
el.value = '';
el.dispatchEvent(new Event('input', { bubbles: true }));
}
function findListed(value) {
var found = null;
$list.find('input[type="checkbox"]').each(function () {
if (this.value === value) { found = this; return false; }
});
return found;
}
// Invariant: "No Major Restriction" is shown and enabled if and only if no other major is checked.
function syncNoRestriction() {
if (!$noneItem.length) { return; }
var others = $list.find('input[type="checkbox"]:checked').not($noneBox).length;
var wasHidden = $noneItem.prop('hidden');
$noneItem.prop('hidden', others > 0);
$noneBox.prop('disabled', others > 0);
if (wasHidden && !others) {
$noneBox.prop('checked', true);
announce('No majors selected. ' + NO_RESTRICTION + ' is selected again.');
}
}
function addPending() {
var value = pendingValue();
if (!value) {
announce('Choose a major from the suggestion list before adding it.');
if (field()) { field().focus(); }
return;
}
var existing = findListed(value);
if (existing) {
existing.checked = true;
announce(value + ' is already listed and is selected.');
} else {
var li = document.createElement('li');
var label = document.createElement('label');
var box = document.createElement('input');
box.type = 'checkbox';
box.className = 'd-inline';
box.name = 'student_major:list';
box.value = value;
box.checked = true;
label.appendChild(box);
label.appendChild(document.createTextNode(' ' + value));
li.appendChild(label);
$list.append(li);
announce(value + ' added to majors.');
}
clearField();
updateButton();
syncNoRestriction();
if (field()) { field().focus(); }
}
var container = enhanceInput(input, LISTS.majors, {
autoselect: true,
onConfirm: function () { window.setTimeout(updateButton, 0); }
});
if (!container) { return; }
// Capture phase runs before the library's own keydown handler, so aria-expanded
// still reflects whether this Enter will confirm a suggestion.
container.addEventListener('keydown', function (event) {
var el = field();
if (!el || event.target !== el) { return; }
if (event.key !== 'Enter' && event.keyCode !== 13) { return; }
if (el.getAttribute('aria-expanded') === 'true') { return; }
event.preventDefault();
addPending();
}, true);
container.addEventListener('input', updateButton);
container.addEventListener('keyup', updateButton);
$button.on('click', function (event) {
event.preventDefault();
addPending();
});
$list.on('change', 'input[type="checkbox"]', syncNoRestriction);
updateButton();
syncNoRestriction();
}
$(function () {
if (typeof window.accessibleAutocomplete !== 'function') {
warn('accessible-autocomplete not loaded; fields left as plain inputs');
return;
}
$.each(FIELDS, function (index, config) {
$(config.selector).each(function () {
if (config.picker) {
initMajorPicker(this);
} else {
// Capture these before enhanceInput replaces the element.
var inputId = this.id;
var legacyValue = $.trim(this.value);
var strict = isStrict(this);
var choices = LISTS[config.list];
var container = enhanceInput(this, choices);
if (container && strict) {
initStrictChoice(container, inputId, choices, legacyValue);
}
}
});
});
bindStrictSubmit();
});
})(jQuery);