https://www.saybrook.edu/wp-content/plugins/tcs-client-nf-sync/assets/js/tcsCoreScripts.js?ver=6.8.2
1if (typeof nfForms === 'undefined') var nfForms = []; 2 3let autocompleteSetupInterval; 4 5jQuery(document).ready(() => { 6 if (jQuery('.rmf-gdrp')) jQuery('.rmf-gdrp').hide(); 7 nfForms.forEach(form => { 8 const dateLimit = getDateLimit(form); 9 if (dateLimit > 0) limitDates(dateLimit); 10 }); 11}); 12 13// Add this debug function to check semester data 14function debugSemesterData() { 15 // console.log('ninjaFormDataJS.semestersHTML available:', typeof ninjaFormDataJS.semestersHTML !== 'undefined'); 16 if (typeof ninjaFormDataJS.semestersHTML !== 'undefined') { 17 console.log('semestersHTML content length:', ninjaFormDataJS.semestersHTML.length); 18 console.log('semestersHTML sample:', ninjaFormDataJS.semestersHTML.substring(0, 200)); 19 } 20} 21// End debug code 22 23// ==================== NEW: Program-Campus Mapping Helper Functions ==================== 24 25/** 26 * Get the mapped campuses for a given program value using the program_campus_mapping 27 * from the page configuration. 28 * 29 * @param {string} programValue - The program value to look up 30 * @param {Array} mappingData - The program_campus_mapping array from pageFieldConfig 31 * @returns {Array|null} Array of campus values mapped to the program, or null if not found 32 */ 33function getMappedCampusesForProgram(programValue, mappingData) { 34 if (!mappingData || !Array.isArray(mappingData) || !programValue) { 35 return null; 36 } 37 38 const mapping = mappingData.find(item => item.program === programValue); 39 return mapping && mapping.campuses ? mapping.campuses : null; 40} 41 42/** 43 * Get the program-campus mapping data from the page configuration. 44 * 45 * @returns {Array|null} The program_campus_mapping array or null if not found 46 */ 47function getProgramCampusMappingData() { 48 const pageConfig = ninjaFormDataJS.pageFieldConfig; 49 if (pageConfig && pageConfig.program_campus_mapping && Array.isArray(pageConfig.program_campus_mapping) && pageConfig.program_campus_mapping.length > 0) { 50 return pageConfig.program_campus_mapping; 51 } 52 return null; 53} 54 55/** 56 * Check if a program has campus mapping defined. 57 * 58 * @param {string} programValue - The program value to check 59 * @param {Array} mappingData - The program_campus_mapping array 60 * @returns {boolean} True if the program has a mapping with at least one campus 61 */ 62function hasProgramMapping(programValue, mappingData) { 63 if (!mappingData || !Array.isArray(mappingData) || !programValue) { 64 return false; 65 } 66 const mapping = mappingData.find(item => item.program === programValue); 67 return mapping && mapping.campuses && mapping.campuses.length > 0; 68} 69 70// ==================== END NEW FUNCTIONS ==================== 71 72jQuery(document).on('nfFormReady', (e, layoutView) => { 73 74 // debugSemesterData(); 75 76 const form = nfForms.find(f => layoutView.$el[0].id === `nf-form-${f.id}-cont`); 77 if (typeof form !== 'undefined') { 78 // Set global form context for rule engine 79 window.currentFormContext = form; 80 81 // Initialize global field variables 82 schoolFieldVal = getSchoolSelectField(form); 83 schoolLabelVal = getSchoolLabelField(form); 84 campusFieldVal = getCampusSelectField(form); 85 campusLabelVal = getCampusLabelField(form); 86 campusDataFieldVal = getCampusDataField(form); 87 programFieldVal = getProgramSelectField(form); 88 programLabelVal = getProgramLabelField(form); 89 semesterFieldVal = getSemesterSelectField(form); 90 semesterLabelVal = getSemesterLabelField(form); 91 countryFieldIdVal = getCountryField(form); 92 stateFieldIdVal = getStateField(form); 93 94 const submitButton = getSubmitButton(form); 95 const pageUrlField = getPageUrlField(form); 96 if (typeof pageUrlField !== 'undefined') captureURL(pageUrlField.id); 97 const thankYouPageField = getThankYouPageField(form); 98 if (typeof thankYouPageField !== 'undefined') setThankYouPage(thankYouPageField.id); 99 const srcParamField = getSrcParamTranslatedField(form); 100 if (typeof srcParamField !== 'undefined') getLeadSourceID(srcParamField.id); 101 102 // Fields for dropdowns 103 const schoolField = getSchoolSelectField(form); 104 const schoolLabel = getSchoolLabelField(form); 105 const campusField = getCampusSelectField(form); 106 const campusLabel = getCampusLabelField(form); 107 const campusDataField = getCampusDataField(form); 108 const programField = getProgramSelectField(form); 109 const programLabel = getProgramLabelField(form); 110 const semesterField = getSemesterSelectField(form); 111 const semesterLabel = getSemesterLabelField(form); 112 const countryFieldId = getCountryField(form) 113 const stateFieldId = getStateField(form) 114 115 // Determine the chaining order 116 const useDynamic = useDynamicCampusProgram(form); 117 118 // Handle forms with SCHOOL > PROGRAM > CAMPUS > SEMESTER chain (useDynamic === 'program') 119 console.log('schoolField', schoolField); 120 console.log('schoolLabel', schoolLabel); 121 console.log('campusField', campusField); 122 console.log('campusLabel', campusLabel); 123 console.log('programField', programField); 124 console.log('programLabel', programLabel); 125 console.log('semesterField', semesterField); 126 console.log('semesterLabel', semesterLabel); 127 128 if (typeof schoolField !== 'undefined' && typeof schoolLabel !== 'undefined' && 129 typeof campusField !== 'undefined' && typeof campusLabel !== 'undefined' && 130 typeof programField !== 'undefined' && typeof programLabel !== 'undefined' && 131 typeof semesterField !== 'undefined' && typeof semesterLabel !== 'undefined') { 132 133 if (useDynamic === 'program') {
134 // Show School > Program > Campus > Semester 135 schoolProgramCampusSemesterChain(schoolField.id, schoolLabel.id, programField.id, programLabel.id, campusField.id, campusLabel.id, semesterField.id, semesterLabel.id, countryFieldId, stateFieldId); 136 } else if (useDynamic === 'campus') { 137 // Show School > Campus > Program > Semester 138 schoolCampusProgramSemesterChain(schoolField.id, schoolLabel.id, programField.id, programLabel.id, campusField.id, campusLabel.id, semesterField.id, semesterLabel.id, countryFieldId, stateFieldId); 139 } else { 140 // Non-chained dropdown behavior for school forms - load all options 141 nonChainedSchoolDropDowns(schoolField.id, schoolLabel.id, programField.id, programLabel.id, campusField.id, campusLabel.id, semesterField.id, semesterLabel.id, countryFieldId, stateFieldId); 142 } 143 144 } 145 146 // Handle forms with PROGRAM > CAMPUS > SEMESTER chain (no school) - useDynamic === 'program' 147 else if (typeof campusField !== 'undefined' && typeof campusLabel !== 'undefined' && 148 typeof programField !== 'undefined' && typeof programLabel !== 'undefined' && 149 typeof semesterField !== 'undefined' && typeof semesterLabel !== 'undefined') { 150 151 if (useDynamic === 'program') { 152 // Show program > campus > semester chain 153 programCampusSemesterChainNoSchool(programField.id, programLabel.id, campusField.id, campusLabel.id, semesterField.id, semesterLabel.id, countryFieldId, stateFieldId); 154 } else if (useDynamic === 'campus') { 155 // Show campus > program > semester chain 156 campusProgramSemesterChainNoSchool(programField.id, programLabel.id, campusField.id, campusLabel.id, semesterField.id, semesterLabel.id, countryFieldId, stateFieldId); 157 } else { 158 // Non-chained dropdown behavior 159 nonChainedDropDownsNoSchool(programField.id, programLabel.id, campusField.id, campusLabel.id, semesterField.id, semesterLabel.id, campusDataField, countryFieldId, stateFieldId); 160 } 161 162 const utmProgramField = getUtmProgramField(form); 163 const utmCampusField = getUtmCampusField(form); 164 if (typeof utmCampusField !== 'undefined' && typeof utmCampusField.value !== 'undefined') { 165 selectProgramCampusUTM(programField.id, programLabel.id, campusField.id, campusLabel.id, campusDataField, utmCampusField, utmProgramField, useDynamic); 166 } 167 } 168 169 // Handle forms with only PROGRAM (no campus/semester) 170 else if (typeof programField !== 'undefined' && typeof programLabel !== 'undefined') { 171 const utmProgramField = getUtmProgramField(form); 172 selectProgramUTM(programField.id, programLabel.id, utmProgramField); 173 } 174 // Handle forms with only SCHOOL (no program/campus) 175 else if (typeof schoolField !== 'undefined' && typeof schoolLabel !== 'undefined') { 176 initializeSchoolField(schoolField.id, schoolLabel.id); 177 const utmSchoolField = getUtmSchoolField(form); 178 if (typeof utmSchoolField !== 'undefined') { 179 selectSchoolUTM(schoolField.id, schoolLabel.id, utmSchoolField); 180 } 181 } 182 183 // Pre-select the "Campus of Interest" and "Program of Interest" saved 184 // in the page's TCS Field Config (backend), if set. Both dropdowns are 185 // still populated with their full option lists (see the chain 186 // functions above), so this only sets the *default* selection -- the 187 // visitor remains free to pick a different Campus afterwards, and the 188 // existing Campus change handler will refilter the Program list to 189 // match dynamically. 190 const pageConfigForDefaults = ninjaFormDataJS.pageFieldConfig; 191 if (pageConfigForDefaults && typeof campusField !== 'undefined' && typeof programField !== 'undefined') { 192 setTimeout(function () { 193 const $campusSelectEl = jQuery("#nf-field-" + campusField.id); 194 const $programSelectEl = jQuery("#nf-field-" + programField.id);
195 const defaultCampus = pageConfigForDefaults.campus; 196 const defaultProgram = pageConfigForDefaults.program_of_interest; 197 198 function selectSavedProgram() { 199 if (defaultProgram && $programSelectEl.find('option[value="' + defaultProgram + '"]').length) { 200 $programSelectEl.val(defaultProgram).trigger('change'); 201 } 202 } 203 204 if (useDynamic === 'campus' && defaultCampus && $campusSelectEl.find('option[value="' + defaultCampus + '"]').length) { 205 // Campus-first chain: set the Campus first so its change 206 // handler rebuilds the Program list scoped to it, then 207 // apply the saved Program default from that filtered list. 208 $campusSelectEl.val(defaultCampus).trigger('change'); 209 selectSavedProgram(); 210 } else { 211 // Program-first (or non-chained) forms: keep the original 212 // behavior of selecting the Program first, then also apply 213 // the saved Campus default, if present. 214 selectSavedProgram(); 215 if (defaultCampus && $campusSelectEl.find('option[value="' + defaultCampus + '"]').length) { 216 $campusSelectEl.val(defaultCampus).trigger('change'); 217 } 218 } 219 }, 0); 220 } else if (pageConfigForDefaults && pageConfigForDefaults.program_of_interest && typeof programField !== 'undefined') { 221 // Fallback for forms with a Program field but no Campus field. 222 setTimeout(function () { 223 const $programSelectEl = jQuery("#nf-field-" + programField.id); 224 if ($programSelectEl.find('option[value="' + pageConfigForDefaults.program_of_interest + '"]').length) { 225 $programSelectEl.val(pageConfigForDefaults.program_of_interest).trigger('change'); 226 } 227 }, 0); 228 } 229 230 // Rest of your existing code for other form fields... 231 const timeZoneField = getTimezoneField(form); 232 if (typeof timeZoneField !== 'undefined' && typeof submitButton !== 'undefined') bindChangesPartChange(timeZoneField.id, submitButton.id); 233 const countryField = getCountryField(form); 234 const countryZipTypeField = getCountryZipTypeField(form); 235 const zipStatusField = getZipStatusField(form); 236 const zipCodeField = getZipCodeField(form); 237 const postalField = getPostalCodeField(form); 238 const stateField = getStateField(form); 239 const provinceField = getProvinceField(form); 240 const intStateField = getInternationalStateField(form); 241 const cityField = getCityField(form); 242 if (typeof countryField !== 'undefined' && typeof countryZipTypeField !== 'undefined' && 243 typeof zipStatusField !== 'undefined' && typeof zipCodeField !== 'undefined' && 244 typeof postalField !== 'undefined' && typeof stateField !== 'undefined' && 245 typeof intStateField !== 'undefined' && typeof cityField !== 'undefined') { 246 247 validateZipCode(countryField.id, countryZipTypeField.id, zipCodeField.id, postalField.id, stateField.id, provinceField.id, intStateField.id, zipStatusField.id, cityField.id, submitButton.id); 248 autocompleteSetupInterval = setInterval(() => { 249 setupAddressAutocomplete(countryField.id); 250 }); 251 } 252 jQuery(`#nf-form-${form.id}-cont .nf-next`).click(() => { 253 const latField = getLocalLatField(form); 254 const longField = getLocalLongField(form); 255 if (typeof latField !== 'undefined' && typeof longField !== 'undefined') { 256 getLatLong(latField.id, longField.id, submitButton.id); 257 } 258 const countryField = getCountryField(form); 259 if (typeof countryField !== 'undefined') { 260 setupAddressAutocomplete(countryField.id); 261 } 262 if (typeof countryCodeField !== 'undefined') gdprRequirements(countryCodeField.id); 263 }); 264 setEmailValidation(); 265 } 266}); 267 268// ==================== CHAINING FUNCTIONS ==================== 269// Helper function to initialize dropdown with default option 270function initializeDropdownWithDefault(fieldID, defaultText) { 271 const $field = jQuery("#nf-field-" + fieldID); 272 if ($field.length) { 273 $field.find('option').remove(); 274 $field.find('optgroup').remove(); 275 $field.append(`<option value="" label="">${defaultText || '--'}</option>`); 276 } 277} 278 279// Auto-select a dropdown's only real option (i.e. excluding the blank/placeholder 280// option) so the user isn't forced to manually pick a value when there is only one 281// available choice. Used for Program, Campus, and Semester fields. 282function autoSelectSingleOption(fieldID) { 283 if (!fieldID) return false;
284 const $select = jQuery("#nf-field-" + fieldID); 285 if (!$select.length) return false; 286 287 const optionsWithValue = $select.find("option").filter(function () { 288 const val = jQuery(this).val(); 289 return val && val.trim() !== ""; 290 }); 291 292 if (optionsWithValue.length !== 1) return false; 293 294 const onlyValue = optionsWithValue.eq(0).val(); 295 if ($select.val() === onlyValue) return false; // already selected, avoid re-triggering 296 297 optionsWithValue.prop("selected", true); 298 $select.trigger('change'); 299 return true; 300} 301 302// ==================== UPDATED: Filter campuses by program using mapping or data-class fallback ==================== 303 304/** 305 * Filter campus dropdown options based on the selected program. 306 * First tries to use program_campus_mapping from page config. 307 * If no mapping found for the program, falls back to data-class filtering. 308 * 309 * @param {string} programValue - The selected program value 310 * @param {string} campusID - The ID of the campus dropdown field 311 * @param {string} programID - The ID of the program dropdown field (needed for data-class fallback) 312 * @returns {boolean} True if mapping was applied, false if using data-class fallback 313 */ 314function filterCampusesByProgramMapping(programValue, campusID, programID) { 315 const mappingData = getProgramCampusMappingData(); 316 317 // Check if mapping exists for this specific program 318 if (mappingData && programValue) { 319 const mappedCampuses = getMappedCampusesForProgram(programValue, mappingData); 320 321 // If mapping exists for this program, use it 322 if (mappedCampuses !== null && mappedCampuses.length > 0) { 323 // Get the campus dropdown element 324 const $campusSelect = jQuery("#nf-field-" + campusID); 325 326 // Filter campus options based on mapping 327 $campusSelect.find('option').each(function() { 328 const $option = jQuery(this); 329 const optionValue = $option.val(); 330 331 // Skip the default/placeholder option 332 if (optionValue === '') return; 333 334 // Check if this campus is in the mapped campuses list 335 const shouldShow = mappedCampuses.includes(optionValue); 336 337 if (shouldShow) { 338 $option.show(); 339 $option.prop('disabled', false); 340 } else { 341 $option.hide(); 342 if ($option.is(':selected')) { 343 $option.prop('selected', false); 344 } 345 $option.prop('disabled', true); 346 } 347 }); 348 349 // After filtering, check if only one campus remains and auto-select it 350 const visibleOptions = $campusSelect.find('option').filter(function() { 351 return jQuery(this).val() !== '' && jQuery(this).is(':visible'); 352 }); 353 354 if (visibleOptions.length === 1) { 355 visibleOptions.prop('selected', true); 356 $campusSelect.trigger('change'); 357 } 358 359 return true; // Mapping was applied 360 } 361 } 362 363 // No mapping found for this program - FALL BACK TO ORIGINAL data-class filtering 364 if (programValue && programID) { 365 // Get the selected program option to read its data-class attribute 366 const $selectedProgram = jQuery("#nf-field-" + programID + " option:selected"); 367 let optionCampuses = $selectedProgram.attr('data-class'); 368 369 if (typeof optionCampuses !== 'undefined' && optionCampuses) { 370 optionCampuses = optionCampuses.split(' '); 371 const $campusSelect = jQuery("#nf-field-" + campusID); 372 373 $campusSelect.find('option').each(function() { 374 const $option = jQuery(this); 375 const optionValue = $option.val(); 376 377 // Skip the default/placeholder option 378 if (optionValue === '') return; 379 380 // Check if this campus is in the data-class list 381 const shouldShow = optionCampuses.indexOf(optionValue) !== -1; 382 383 if (shouldShow) { 384 $option.show(); 385 $option.prop('disabled', false); 386 } else { 387 $option.hide(); 388 if ($option.is(':selected')) { 389 $option.prop('selected', false); 390 } 391 $option.prop('disabled', true); 392 } 393 }); 394 395 // After filtering, check if only one campus remains and auto-select it 396 const visibleOptions = $campusSelect.find('option').filter(function() { 397 return jQuery(this).val() !== '' && jQuery(this).is(':visible'); 398 }); 399 400 if (visibleOptions.length === 1) { 401 visibleOptions.prop('selected', true); 402 $campusSelect.trigger('change'); 403 } 404 405 return false; // Using data-class fallback 406 } 407 } 408 409 // If no filtering method works, show all campuses 410 const $campusSelect = jQuery("#nf-field-" + campusID); 411 $campusSelect.find('option').show(); 412 $campusSelect.find('option').prop('disabled', false); 413 414 return false;
415} 416 417/** 418 * Filter programs based on selected campus using program_campus_mapping. 419 * Used in campus-first chains. Falls back to original behavior if no mapping. 420 * 421 * @param {string} campusValue - The selected campus value 422 * @param {string} programID - The ID of the program dropdown field 423 * @returns {boolean} True if mapping was applied, false if using fallback 424 */ 425function filterProgramsByCampusMapping(campusValue, programID) { 426 const mappingData = getProgramCampusMappingData(); 427 const $programSelect = jQuery("#nf-field-" + programID); 428 let hasMappingForAnyProgram = false; 429 430 // First, check if any program has mapping defined 431 if (mappingData) { 432 $programSelect.find('option').each(function() { 433 const $option = jQuery(this); 434 const optionValue = $option.val(); 435 436 // Skip the default/placeholder option 437 if (optionValue === '') return; 438 439 // Get mapped campuses for this program 440 const mappedCampuses = getMappedCampusesForProgram(optionValue, mappingData); 441 442 // If this program has mapping defined 443 if (mappedCampuses !== null && mappedCampuses.length > 0) { 444 hasMappingForAnyProgram = true; 445 // Show only if mapping includes the selected campus 446 const shouldShow = mappedCampuses.includes(campusValue); 447 if (shouldShow) { 448 $option.show(); 449 $option.prop('disabled', false); 450 } else { 451 $option.hide(); 452 if ($option.is(':selected')) { 453 $option.prop('selected', false); 454 } 455 $option.prop('disabled', true); 456 } 457 } 458 }); 459 } 460 461 // If no programs have mapping defined, fall back to original data-class filtering 462 if (!hasMappingForAnyProgram) { 463 // Reset all options to visible first 464 $programSelect.find('option').show(); 465 $programSelect.find('option').prop('disabled', false); 466 467 // Now apply data-class filtering 468 $programSelect.find('option').each(function() { 469 const $option = jQuery(this); 470 const optionValue = $option.val(); 471 472 // Skip the default/placeholder option 473 if (optionValue === '') return; 474 475 // Get the data-class attribute of this program option 476 const optionCampuses = ($option.attr('data-class') || '').split(' ').filter(
476Boolean); 477 478 // If the program has data-class and the selected campus is in it, keep it 479 // If the program has no data-class (empty), show it (fallback) 480 if (optionCampuses.length > 0) { 481 const shouldShow = optionCampuses.indexOf(campusValue) !== -1; 482 if (!shouldShow) { 483 $option.hide(); 484 if ($option.is(':selected')) { 485 $option.prop('selected', false); 486 } 487 $option.prop('disabled', true); 488 } 489 } 490 // If no data-class, keep it visible (fallback) 491 }); 492 493 return false; 494 } 495 496 return true; 497} 498 499// =========================================================================================================================== 500 501// UNIVERSAL RULE PROCESSOR (Supports "if", AND/OR after-if, before-if definitions) 502 503window.NF_DO_NOT_SUBMIT = false; 504window.NF_DO_NOT_SUBMIT_MSG = ''; 505window.currentFormContext = null; // Store current form context 506 507// These will be set when form is ready 508let schoolFieldVal, schoolLabelVal, campusFieldVal, campusLabelVal, campusDataFieldVal; 509let programFieldVal, programLabelVal, semesterFieldVal, semesterLabelVal; 510let countryFieldIdVal, stateFieldIdVal; 511 512function enforceDoNotSubmit() { 513 if (!window.currentFormContext) return false; 514 515 const form = window.currentFormContext; 516 const submitBtnId = getSubmitButton(form)?.id; 517 if (!submitBtnId) return false; 518 519 const $submitBtn = jQuery('#nf-field-' + submitBtnId); 520 521 if (!$submitBtn.length) return false; 522 523 if (window.NF_DO_NOT_SUBMIT) { 524 $submitBtn.prop('disabled', true).attr('aria-disabled', 'true').addClass('nf-disabled'); 525 showDO_NOT_SUBMIT_error(submitBtnId, window.NF_DO_NOT_SUBMIT_MSG); 526 } else { 527 $submitBtn.prop('disabled', false).removeAttr('aria-disabled').removeClass('nf-disabled'); 528 hideDO_NOT_SUBMIT_error(submitBtnId); 529 } 530 531 return true; 532} 533 534function runRuleEngineAfterChange() { 535 console.log('runRuleEngineAfterChange'); 536 537 const values = { 538 program: sessionStorage.getItem("nf_program") || jQuery("#nf-field-" + programFieldVal?.id)?.val(), 539 campus: sessionStorage.getItem("nf_campus") || jQuery("#nf-field-" + campusFieldVal?.id)?.val(), 540 semester: sessionStorage.getItem("nf_semester") || jQuery("#nf-field-" + semesterFieldVal?.id)?.val(), 541 country: sessionStorage.getItem("nf_country") || jQuery("#nf-field-" + countryFieldIdVal?.id)?.val(), 542 state: sessionStorage.getItem("nf_state") || jQuery("#nf-field-" + stateFieldIdVal?.id)?.val(), 543 }; 544 545 const fieldIDs = { 546 program: programFieldVal?.id, 547 campus: campusFieldVal?.id, 548 semester: semesterFieldVal?.id, 549 country: countryFieldIdVal?.id, 550 state: stateFieldIdVal?.id 551 }; 552 553 // STEP 1: RESET everything first 554 resetRuleEffects(fieldIDs); 555 556 // STEP 2: Apply rules fresh 557 processRules(ninjaFormDataJS.rulesData, values, fieldIDs); 558} 559 560var DoNotSubmitController = Backbone.Model.extend({ 561 initialize: function () { 562 // Step change (multi-part) 563 this.listenTo(nfRadio.channel('nfMP'), 'change:part', this.enforce); 564 565 // Submit button rendered / re-rendered 566 this.listenTo(nfRadio.channel('forms'), 'submit:ready', this.enforce); 567 }, 568 569 enforce: function () { 570 // Set up field change handlers once 571 if (countryFieldIdVal?.id) { 572 jQuery("#nf-field-" + countryFieldIdVal.id).off('change').on('change', function () { 573 runRuleEngineAfterChange(); 574 console.log('country change') 575 }); 576 } 577 578 if (stateFieldIdVal?.id) { 579 jQuery("#nf-field-" + stateFieldIdVal.id).off('change').on('change', function () { 580 runRuleEngineAfterChange(); 581 console.log('state change') 582 }); 583 } 584 585 // Run rule engine once 586 runRuleEngineAfterChange(); 587 } 588}); 589 590jQuery(document).ready(function () { 591 if (typeof nfRadio !== 'undefined') { 592 console.log("Page Change") 593 new DoNotSubmitController(); 594 } 595}); 596 597jQuery(document).on('nfFormReady', (e, layoutView) => { 598 // Run once when form is ready 599 setTimeout(enforceDoNotSubmit, 300); 600 601 // Re-check every time ANY field changes value 602 Backbone.Radio.channel('fields').on('change:modelValue', function (model) { 603 enforceDoNotSubmit(); 604 }); 605 606 // Re-check on part (page) change — very common source of re-enabling 607 const mpChannel = Backbone.Radio.channel('nfMP'); 608 mpChannel.on('change:part', function () { 609 setTimeout(enforceDoNotSubmit, 150); 610 }); 611 612 // Also useful in many Ninja Forms setups: 613 jQuery(document).on('change', '.ninja-forms-field', function () { 614 setTimeout(enforceDoNotSubmit, 100); 615 }); 616 617 // Bonus: after validation run 618 Backbone.Radio.channel('submit').on('validate:field', function () { 619 setTimeout(enforceDoNotSubmit, 120); 620 }); 621}); 622 623function beforeIFCompleted(beforeIF, values) { 624 console.log('beforeIF', beforeIF) 625 console.log('values', values) 626 if (!beforeIF.length) return false;
627 628 let status = false; 629 let first = true; 630 631 beforeIF.forEach(act => { 632 633 const valueMatch = (values[act.rule_sub_field] == act.rule_sub_value); 634 635 // FIRST ITEM (do-not-submit block) 636 if (first) { 637 status = valueMatch; 638 first = false; 639 return; 640 } 641 642 // OR logic 643 if (act.rule_sub_function === "or") { 644 status = status || valueMatch; 645 } 646 647 // AND logic 648 if (act.rule_sub_function === "and") { 649 status = status && valueMatch; 650 } 651 }); 652 653 return status; 654} 655 656function processRules(rules, values, fieldIDs) { 657 console.log('Rules', rules); 658 console.log('values', values); 659 console.log('fieldIDs', fieldIDs); 660 661 Object.keys(rules).forEach(ruleId => { 662 663 const rule = rules[ruleId]; 664 const nested = rule.rule_nested_function || []; 665 666 let beforeIF = []; 667 let afterIF = []; 668 let mainIF = null; 669 let foundIF = false; 670 671 // Always push main action (hide/show) 672 beforeIF.push({ 673 rule_sub_function: rule.rule_function, 674 rule_sub_field: rule.rule_field, 675 rule_sub_value: rule.rule_value 676 }); 677 678 // SPLIT: beforeIF / IF / afterIF 679 nested.forEach(item => { 680 if (item.rule_sub_function === "if") { 681 foundIF = true; 682 mainIF = item; 683 afterIF.push(item); // IF stays inside afterIF group 684 } else if (!foundIF) { 685 beforeIF.push(item); // multiple hide actions here 686 } else { 687 afterIF.push(item); // OR & AND go here 688 } 689 }); 690 691 if (!mainIF) return; 692 693 let status = false; // default OR-logic safe start 694 let first = true; 695 696 afterIF.forEach(item => { 697 698 if (item.rule_sub_function === "if") { 699 status = (values[item.rule_sub_field] == item.rule_sub_value); 700 first = false; 701 } 702 703 if (item.rule_sub_function === "or") { 704 status = status || (values[item.rule_sub_field] == item.rule_sub_value); 705 } 706 707 if (item.rule_sub_function === "and") { 708 status = status && (values[item.rule_sub_field] == item.rule_sub_value); 709 } 710 711 }); 712 713 // APPLY FINAL RESULT TO ALL hide actions 714 if (status && rule.rule_function === "hide") { 715 console.log('hide'); 716 beforeIF.forEach(act => { 717 hideField(fieldIDs[act.rule_sub_field], act.rule_sub_value); 718 }); 719 } 720 else if (status && rule.rule_function === "do-not-submit") { 721 console.log('do not submit'); 722 723 // Check: BeforeIF fields also must be selected 724 const beforeDone = beforeIFCompleted(beforeIF, values); 725 console.log('beforeDone', beforeDone); 726 727 if (beforeDone) { 728 console.log('iff do not submit'); 729 const form = window.currentFormContext; 730 if (!form) return; 731 const submitBtnId = getSubmitButton(form).id; 732 const $submitBtn = jQuery('#nf-field-' + submitBtnId); 733 734 window.NF_DO_NOT_SUBMIT = true; 735 window.NF_DO_NOT_SUBMIT_MSG = rule?.rule_dns_msg || "You cannot submit this form."; 736 737 $submitBtn.prop('disabled', true).addClass('nf-disabled'); 738 739 const errorMsg = rule.rule_dns_msg || "You cannot submit this form."; 740 showDO_NOT_SUBMIT_error(submitBtnId, errorMsg); 741 742 } else { 743 console.log('else do not submit'); 744 // Before fields NOT completed → keep button enabled 745 const form = window.currentFormContext; 746 if (!form) return; 747 const submitBtnId = getSubmitButton(form).id; 748 const $submitBtn = jQuery('#nf-field-' + submitBtnId); 749 750 window.NF_DO_NOT_SUBMIT = false;
751 window.NF_DO_NOT_SUBMIT_MSG = ""; 752 $submitBtn.prop('disabled', false).removeClass('nf-disabled'); 753 hideDO_NOT_SUBMIT_error(submitBtnId); 754 } 755 } 756 757 }); 758} 759 760function showDO_NOT_SUBMIT_error(fieldID, message) { 761 let $field = jQuery('#nf-field-' + fieldID); 762 let $container = $field.closest('.submit-container'); 763 764 // Remove old message if exists 765 $container.find('.rule-error').remove(); 766 767 // Add new message 768 $container.append( 769 `<div class="rule-error" style="color:#c00; margin-top:5px; font-size:13px;">${message}</div>` 770 ); 771} 772 773function hideDO_NOT_SUBMIT_error(fieldID) { 774 let $field = jQuery('#nf-field-' + fieldID); 775 let $container = $field.closest('.submit-container'); 776 777 // Remove old message if exists 778 $container.find('.rule-error').remove(); 779} 780 781function hideField(fieldId, fieldValue) { 782 const $el = jQuery('#nf-field-' + fieldId); 783 784 const oldVal = $el.val(); 785 786 // Remove option 787 const removed = $el.find('option[value="' + fieldValue + '"]'); 788 if (!removed.length) return; // nothing to do 789 790 // removed.remove(); 791 // removed.hide(); 792 removed.prop('disabled', true); 793 794 // If the removed value was selected → reset value & trigger change 795 if (oldVal == fieldValue) { 796 $el.val(''); 797 $el.trigger('change'); 798 $el.find('option[value="' + fieldValue + '"]').prop('disabled', true); 799 } 800} 801 802function showField(fieldName) { 803 const el = jQuery('#nf-field-' + fieldName); 804 el.find('option').show(); // show all again 805 el.find('option').prop('disabled', false); 806} 807 808function resetRuleEffects(fieldIDs) { 809 // Re-enable all options in all rule fields 810 Object.values(fieldIDs).forEach(fieldId => { 811 const $el = jQuery('#nf-field-' + fieldId); 812 if ($el.length) { 813 $el.find('option').prop('disabled', false); 814 } 815 }); 816 817 // Enable submit button 818 if (window.currentFormContext) { 819 const submitBtn = getSubmitButton(window.currentFormContext); 820 // Guard: some forms don't have a field whose key includes "submit", 821 // so getSubmitButton() can legitimately return undefined. Without 822 // this check, reading .id off it throws and aborts whatever called 823 // resetRuleEffects() (runRuleEngine(), called synchronously during 824 // form init), which in turn was silently preventing the 825 // Program/Campus "of Interest" preselect code later in the 826 // nfFormReady handler from ever running. 827 if (typeof submitBtn !== 'undefined' && submitBtn) { 828 const submitBtnId = submitBtn.id; 829 const $submitBtn = jQuery('#nf-field-' + submitBtnId); 830 $submitBtn.prop('disabled', false).removeClass('nf-disabled'); 831 hideDO_NOT_SUBMIT_error(submitBtnId); 832 } 833 } 834} 835 836// =========================================================================================================================== 837 838// SCHOOL > PROGRAM > CAMPUS > SEMESTER chain (useDynamic === 'program') 839function schoolProgramCampusSemesterChain(schoolID, schoolLabelID, programID, programLabelID, campusID, campusLabelID, semesterID, semesterLabelID, countryFieldId, stateFieldId) { 840 // Initialize all dropdowns with appropriate default options 841 initializeDropdownWithDefault(schoolID, 'Choose School'); 842 initializeDropdownWithDefault(programID, '--'); 843 initializeDropdownWithDefault(campusID, '--'); 844 initializeDropdownWithDefault(semesterID, '--'); 845 846 // Initialize school field (no default option needed as it should show all schools) 847 loadSchoolOptions(schoolID, schoolLabelID); 848 849 // School -> Program filtering 850 jQuery("#nf-field-" + schoolID).change(function () { 851 const schoolValue = jQuery(this).val(); 852 853 // Reset program dropdown with default option 854 initializeDropdownWithDefault(programID, 'Choose Program'); 855 856 if (schoolValue) { 857 jQuery("#nf-field-" + programID).append(ninjaFormDataJS.programsHTML); 858 859 // Filter programs based on selected school - USING data-school-id 860 jQuery("#nf-field-" + programID + " option").each((index, option) => { 861 let optionSchoolId = jQuery(option).attr('data-school-id'); 862 863 if (typeof optionSchoolId !== 'undefined' && schoolValue !== '' && option.value !== '') { 864 // Compare the school ID directly 865 if (optionSchoolId !== schoolValue) { 866 jQuery(option).remove(); 867 } 868 } 869 }); 870 871 jQuery("#nf-field-" + programID + " optgroup").each((index, option) => { 872 if (jQuery(option).is(':empty')) { 873 jQuery(option).remove(); 874 } 875 }); 876 } 877 878 // Reset dependent fields to default (Campus and Semester) 879 initializeDropdownWithDefault(campusID, '--'); 880 initializeDropdownWithDefault(semesterID, '--'); 881 882 const schoolLabel = jQuery("#nf-field-" + schoolID + " option:selected").text(); 883 jQuery("#nf-field-" + schoolLabelID).val(schoolLabel).trigger('change'); 884 885 // If only one Program remains available, auto-select it instead of 886 // requiring the user to choose it manually. 887 autoSelectSingleOption(programID); 888 889 runRuleEngine(); 890 }); 891 892 // Program -> Campus filtering - UPDATED to use program_campus_mapping with fallback 893 jQuery("#nf-field-" + programID).change(function () { 894 const programValue = jQuery(this).val(); 895 896 // Reset campus dropdown with default option 897 initializeDropdownWithDefault(campusID, 'Choose Campus'); 898 899 if (programValue) { 900 jQuery("#nf-field-" + campusID).append(ninjaFormDataJS.campusesHTML); 901 902 // Try to filter using mapping, fallback to data-class if no mapping 903 filterCampusesByProgramMapping(programValue, campusID, programID); 904 905 jQuery("#nf-field-" + campusID + " optgroup").each((index, option) => { 906 if (jQuery(option).is(':empty')) { 907 jQuery(option).remove(); 908 } 909 }); 910 } 911 912 // Reset dependent field (Semester) to default 913 initializeDropdownWithDefault(semesterID, '--'); 914 915 const programLabel = jQuery("#nf-field-" + programID + " option:selected").text(); 916 jQuery("#nf-field-" + programLabelID).val(programLabel).trigger('change'); 917 918 // If only one Campus remains available, auto-select it instead of 919 // requiring the user to choose it manually. 920 autoSelectSingleOption(campusID); 921 922 runRuleEngine(); 923 }); 924 925 // Campus -> Semester filtering - FIXED VERSION 926 jQuery("#nf-field-" + campusID).change(function () { 927 const campusValue = jQuery(this).val(); 928 const programValue = jQuery("#nf-field-" + programID).val(); // Get current program value 929 930 // Reset semester dropdown with default option 931 initializeDropdownWithDefault(semesterID, 'Choose Semester'); 932 933 if (campusValue && programValue) { 934 jQuery("#nf-field-" + semesterID).append(ninjaFormDataJS.semestersHTML); 935 936 // Filter semesters based on selected program AND campus 937 jQuery("#nf-field-" + semesterID + " option").each((index, option) => { 938 let optionSemesters = jQuery(option).attr('data-class'); 939 940 if (typeof optionSemesters !== 'undefined' && campusValue !== '' && programValue !== '') { 941 // The data-class contains pairs like "67-104 67-802" 942 // We need to check if any pair matches the selected program and campus 943 const semesterPairs = optionSemesters.split(' '); 944 let shouldKeep = false;
945 946 semesterPairs.forEach(pair => { 947 const [programId, campusId] = pair.split('-'); 948 if (programId === programValue && campusId === campusValue) { 949 shouldKeep = true; 950 } 951 }); 952 953 if (!shouldKeep && option.value !== '') { 954 jQuery(option).remove(); 955 } 956 } 957 }); 958 959 jQuery("#nf-field-" + semesterID + " optgroup").each((index, option) => { 960 if (jQuery(option).is(':empty')) { 961 jQuery(option).remove(); 962 } 963 }); 964 965 singleSemesterSelect(semesterID, campusID); 966 } 967 968 const campusLabel = jQuery("#nf-field-" + campusID + " option:selected").text(); 969 jQuery("#nf-field-" + campusLabelID).val(campusLabel).trigger('change'); 970 runRuleEngine(); 971 }); 972 973 // Semester change handler 974 jQuery("#nf-field-" + semesterID).change(function () { 975 const semesterLabel = jQuery("#nf-field-" + semesterID + " option:selected").text(); 976 jQuery("#nf-field-" + semesterLabelID).val(semesterLabel).trigger('change'); 977 runRuleEngine(); 978 }); 979 980 jQuery("#nf-field-" + countryFieldId?.id).change(function () { 981 runRuleEngine(); 982 }) 983 jQuery("#nf-field-" + stateFieldId?.id).change(function () { 984 runRuleEngine(); 985 }) 986 987 function runRuleEngine() { 988 989 const values = { 990 school: jQuery("#nf-field-" + schoolID).val(), 991 program: jQuery("#nf-field-" + programID).val(), 992 campus: jQuery("#nf-field-" + campusID).val(), 993 semester: jQuery("#nf-field-" + semesterID).val(), 994 country: jQuery("#nf-field-" + countryFieldId?.id)?.val(), 995 state: jQuery("#nf-field-" + stateFieldId?.id)?.val(), 996 }; 997 998 const fieldIDs = { 999 school: schoolID, 1000 program: programID, 1001 campus: campusID, 1002 semester: semesterID, 1003 country: countryFieldId?.id, 1004 state: stateFieldId?.id 1005 }; 1006 1007 // STEP 1: RESET everything first 1008 resetRuleEffects(fieldIDs); 1009 1010 // STEP 2: Apply rules fresh 1011 processRules(ninjaFormDataJS.rulesData, values, fieldIDs); 1012 } 1013 1014 runRuleEngine(); // run once on load 1015} 1016 1017// ========================================================================================================================================================================================= 1018 1019// SCHOOL > CAMPUS > PROGRAM > SEMESTER chain (useDynamic === 'campus') 1020function schoolCampusProgramSemesterChain(schoolID, schoolLabelID, programID, programLabelID, campusID, campusLabelID, semesterID, semesterLabelID, countryFieldId, stateFieldId) { 1021 // Initialize all dropdowns with appropriate default options 1022 initializeDropdownWithDefault(schoolID, 'Choose School'); 1023 initializeDropdownWithDefault(programID, '--'); 1024 initializeDropdownWithDefault(campusID, '--'); 1025 initializeDropdownWithDefault(semesterID, '--'); 1026 1027 // Initialize school field 1028 loadSchoolOptions(schoolID, schoolLabelID); 1029 1030 // School -> Campus filtering 1031 jQuery("#nf-field-" + schoolID).change(function () { 1032 const schoolValue = jQuery(this).val(); 1033 1034 // Reset campus dropdown with default option 1035 initializeDropdownWithDefault(campusID, 'Choose Campus'); 1036 1037 if (schoolValue) { 1038 jQuery("#nf-field-" + campusID).append(ninjaFormDataJS.campusesHTML); 1039 1040 // Filter campuses based on selected school - USING data-school-id 1041 jQuery("#nf-field-" + campusID + " option").each((index, option) => { 1042 let optionSchoolId = jQuery(option).attr('data-school-id'); 1043 1044 if (typeof optionSchoolId !== 'undefined' && schoolValue !== '' && option.value !== '') { 1045 // Compare the school ID directly 1046 if (optionSchoolId !== schoolValue) { 1047 jQuery(option).remove(); 1048 } 1049 } 1050 }); 1051 1052 jQuery("#nf-field-" + campusID + " optgroup").each((index, option) => { 1053 if (jQuery(option).is(':empty')) { 1054 jQuery(option).remove(); 1055 } 1056 }); 1057 } 1058 1059 // Reset dependent fields to default (Program and Semester) 1060 initializeDropdownWithDefault(programID, '--'); 1061 initializeDropdownWithDefault(semesterID, '--'); 1062 1063 const schoolLabel = jQuery("#nf-field-" + schoolID + " option:selected").text(); 1064 jQuery("#nf-field-" + schoolLabelID).val(schoolLabel).trigger('change'); 1065 1066 // If only one Campus remains available, auto-select it instead of 1067 // requiring the user to choose it manually. 1068 autoSelectSingleOption(campusID); 1069 1070 runRuleEngine(); 1071 }); 1072 1073 // Campus -> Program filtering - UPDATED to use program_campus_mapping 1074 jQuery("#nf-field-" + campusID).change(function () { 1075 const campusValue = jQuery(this).val(); 1076 1077 // Reset program dropdown with default option 1078 initializeDropdownWithDefault(programID, 'Choose Program'); 1079 1080 if (campusValue) { 1081 jQuery("#nf-field-" + programID).append(ninjaFormDataJS.programsHTML); 1082 1083 // Try to filter programs using mapping 1084 const mappingApplied = filterProgramsByCampusMapping(campusValue, programID); 1085 1086 // If no mapping was applied, show all programs 1087 if (!mappingApplied) { 1088 // Filter programs based on selected campus 1089 jQuery("#nf-field-" + programID + " option").each((index, option) => { 1090 let optionCampuses = jQuery(option).attr('data-class'); 1091 if (typeof optionCampuses !== 'undefined' && campusValue !== '') { 1092 optionCampuses = optionCampuses.split(' '); 1093 if (optionCampuses.indexOf(campusValue) === -1 && option.value !== '') { 1094 jQuery(option).remove(); 1095 } 1096 } 1097 }); 1098 } 1099 1100 jQuery("#nf-field-" + programID + " optgroup").each((index, option) => { 1101 if (jQuery(option).is(':empty')) { 1102 jQuery(option).remove(); 1103 } 1104 }); 1105 } 1106 1107 // Reset dependent field (Semester) to default 1108 initializeDropdownWithDefault(semesterID, '--'); 1109 1110 const campusLabel = jQuery("#nf-field-" + campusID + " option:selected").text(); 1111 jQuery("#nf-field-" + campusLabelID).val(campusLabel).trigger('change'); 1112 1113 // If only one Program remains available, auto-select it instead of 1114 // requiring the user to choose it manually. 1115 autoSelectSingleOption(programID); 1116 1117 runRuleEngine(); 1118 }); 1119 1120 // Program -> Semester filtering 1121 jQuery("#nf-field-" + programID).change(function () { 1122 const programValue = jQuery(this).val(); 1123 const campusValue = jQuery("#nf-field-" + campusID).val(); 1124 1125 // Reset semester dropdown with default option 1126 initializeDropdownWithDefault(semesterID, 'Choose Semester'); 1127 1128 if (programValue && campusValue) { 1129 jQuery("#nf-field-" + semesterID).append(ninjaFormDataJS.semestersHTML); 1130 1131 // Filter semesters based on selected program AND campus 1132 jQuery("#nf-field-" + semesterID + " option").each((index, option) => { 1133 let optionSemesters = jQuery(option).attr('data-class'); 1134 let optionPrograms = jQuery(option).attr('data-program-solo-id'); 1135 1136 let shouldRemove = false;
1137 1138 // Filter by campus and program using data-class (format: "program-campus" pairs) 1139 if (typeof optionSemesters !== 'undefined' && campusValue !== '' && programValue !== '') { 1140 const semesterPairs = optionSemesters.split(' '); 1141 let hasMatchingPair = false; 1142 1143 semesterPairs.forEach(pair => { 1144 const [programId, campusId] = pair.split('-'); 1145 if (programId === programValue && campusId === campusValue) { 1146 hasMatchingPair = true; 1147 } 1148 }); 1149 1150 if (!hasMatchingPair && option.value !== '') { 1151 shouldRemove = true; 1152 } 1153 } 1154 1155 // Additional filter by program using data-program-solo-id 1156 if (!shouldRemove && typeof optionPrograms !== 'undefined' && programValue !== '') { 1157 optionPrograms = optionPrograms.split(' '); 1158 if (optionPrograms.indexOf(programValue) === -1 && option.value !== '') { 1159 shouldRemove = true; 1160 } 1161 } 1162 1163 if (shouldRemove) { 1164 jQuery(option).remove(); 1165 } 1166 }); 1167 1168 jQuery("#nf-field-" + semesterID + " optgroup").each((index, option) => { 1169 if (jQuery(option).is(':empty')) { 1170 jQuery(option).remove(); 1171 } 1172 }); 1173 1174 singleSemesterSelect(semesterID, campusID); 1175 } 1176 1177 const programLabel = jQuery("#nf-field-" + programID + " option:selected").text(); 1178 jQuery("#nf-field-" + programLabelID).val(programLabel).trigger('change'); 1179 runRuleEngine(); 1180 }); 1181 1182 // Semester change handler 1183 jQuery("#nf-field-" + semesterID).change(function () { 1184 const semesterLabel = jQuery("#nf-field-" + semesterID + " option:selected").text(); 1185 jQuery("#nf-field-" + semesterLabelID).val(semesterLabel).trigger('change'); 1186 runRuleEngine(); 1187 }); 1188 1189 jQuery("#nf-field-" + countryFieldId?.id).change(function () { 1190 runRuleEngine(); 1191 }) 1192 jQuery("#nf-field-" + stateFieldId?.id).change(function () { 1193 runRuleEngine(); 1194 }) 1195 1196 function runRuleEngine() { 1197 1198 const values = { 1199 school: jQuery("#nf-field-" + schoolID).val(), 1200 program: jQuery("#nf-field-" + programID).val(), 1201 campus: jQuery("#nf-field-" + campusID).val(), 1202 semester: jQuery("#nf-field-" + semesterID).val(), 1203 country: jQuery("#nf-field-" + countryFieldId?.id)?.val(), 1204 state: jQuery("#nf-field-" + stateFieldId?.id)?.val(), 1205 }; 1206 1207 const fieldIDs = { 1208 school: schoolID, 1209 program: programID, 1210 campus: campusID, 1211 semester: semesterID, 1212 country: countryFieldId?.id, 1213 state: stateFieldId?.id 1214 }; 1215 1216 // STEP 1: RESET everything first 1217 resetRuleEffects(fieldIDs); 1218 1219 // STEP 2: Apply rules fresh 1220 processRules(ninjaFormDataJS.rulesData, values, fieldIDs); 1221 } 1222 1223 runRuleEngine(); // run once on load 1224} 1225 1226// PROGRAM > CAMPUS > SEMESTER chain (no school, useDynamic === 'program') - UPDATED 1227function programCampusSemesterChainNoSchool(programID, programLabelID, campusID, campusLabelID, semesterID, semesterLabelID, countryFieldId, stateFieldId) { 1228 console.log('programCampusSemesterChainNoSchool'); 1229 1230 // Initialize program field with all options 1231 const $programSelect = jQuery('#nf-field-' + programID); 1232 $programSelect.find('option').remove(); 1233 $programSelect.find('optgroup').remove(); 1234 $programSelect.append('<option value="" label="">Choose Program</option>'); 1235 $programSelect.append(ninjaFormDataJS.programsHTML); 1236 1237 // Initialize other fields with default options 1238 initializeDropdownWithDefault(campusID, '--'); 1239 initializeDropdownWithDefault(semesterID, '--'); 1240 1241 // Program -> Campus filtering - UPDATED to use program_campus_mapping with fallback 1242 jQuery("#nf-field-" + programID).change(function () { 1243 const programValue = jQuery(this).val(); 1244 1245 // Reset campus dropdown with default option 1246 initializeDropdownWithDefault(campusID, 'Choose Campus'); 1247 // Reset semester dropdown with default option 1248 initializeDropdownWithDefault(semesterID, 'Choose Semester'); 1249 1250 if (programValue) { 1251 jQuery("#nf-field-" + campusID).append(ninjaFormDataJS.campusesHTML); 1252 1253 // Try to filter using mapping, fallback to data-class if no mapping 1254 filterCampusesByProgramMapping(programValue, campusID, programID); 1255 1256 jQuery("#nf-field-" + campusID + " optgroup").each((index, option) => { 1257 if (jQuery(option).is(':empty')) { 1258 jQuery(option).remove(); 1259 } 1260 }); 1261 1262 // Populate Semester filtered by the selected Program alone 1263 jQuery("#nf-field-" + semesterID).append(ninjaFormDataJS.semestersHTML); 1264 1265 jQuery("#nf-field-" + semesterID + " option").each((index, option) => { 1266 if (option.value === '') return; 1267 const optionPrograms = (jQuery(option).attr('data-program-solo-id') || '').split(' ').filter(
1267Boolean); 1268 if (optionPrograms.indexOf(programValue) === -1) { 1269 jQuery(option).remove(); 1270 } 1271 }); 1272 1273 jQuery("#nf-field-" + semesterID + " optgroup").each((index, option) => { 1274 if (jQuery(option).is(':empty')) { 1275 jQuery(option).remove(); 1276 } 1277 }); 1278 } 1279 1280 const programLabel = jQuery("#nf-field-" + programID + " option:selected").text(); 1281 jQuery("#nf-field-" + programLabelID).val(programLabel).trigger('change'); 1282 1283 // If only one Campus remains available, auto-select it instead of 1284 // requiring the user to choose it manually. 1285 autoSelectSingleOption(campusID); 1286 1287 runRuleEngine(); 1288 }); 1289 1290 // Campus -> Semester filtering 1291 jQuery("#nf-field-" + campusID).change(function () { 1292 const campusValue = jQuery(this).val(); 1293 const programValue = jQuery("#nf-field-" + programID).val(); // Get current program value 1294 1295 // Reset semester dropdown with default option 1296 initializeDropdownWithDefault(semesterID, 'Choose Semester'); 1297 1298 if (campusValue && programValue) { 1299 jQuery("#nf-field-" + semesterID).append(ninjaFormDataJS.semestersHTML); 1300 1301 // Filter semesters based on selected campus and program 1302 jQuery("#nf-field-" + semesterID + " option").each((index, option) => { 1303 let optionSemesters = jQuery(option).attr('data-class'); 1304 1305 if (typeof optionSemesters !== 'undefined' && campusValue !== '' && programValue !== '') { 1306 // The data-class contains pairs like "67-104 67-802" 1307 // We need to check if any pair matches the selected program and campus 1308 const semesterPairs = optionSemesters.split(' '); 1309 let shouldKeep = false; 1310 1311 semesterPairs.forEach(pair => { 1312 const [programId, campusId] = pair.split('-'); 1313 if (programId === programValue && campusId === campusValue) { 1314 shouldKeep = true; 1315 } 1316 }); 1317 1318 if (!shouldKeep && option.value !== '') { 1319 jQuery(option).remove(); 1320 } 1321 } 1322 }); 1323 1324 jQuery("#nf-field-" + semesterID + " optgroup").each((index, option) => { 1325 if (jQuery(option).is(':empty')) { 1326 jQuery(option).remove(); 1327 } 1328 }); 1329 1330 singleSemesterSelect(semesterID, campusID); 1331 } 1332 1333 const campusLabel = jQuery("#nf-field-" + campusID + " option:selected").text(); 1334 jQuery("#nf-field-" + campusLabelID).val(campusLabel).trigger('change'); 1335 runRuleEngine(); 1336 }); 1337 1338 // Semester change handler 1339 jQuery("#nf-field-" + semesterID).change(function () { 1340 const semesterLabel = jQuery("#nf-field-" + semesterID + " option:selected").text(); 1341 jQuery("#nf-field-" + semesterLabelID).val(semesterLabel).trigger('change'); 1342 runRuleEngine(); 1343 }); 1344 1345 // Program label change handler 1346 jQuery("#nf-field-" + programID).change(function () { 1347 const programLabel = jQuery("#nf-field-" + programID + " option:selected").text(); 1348 jQuery("#nf-field-" + programLabelID).val(programLabel).trigger('change'); 1349 runRuleEngine(); 1350 }); 1351 1352 jQuery("#nf-field-" + countryFieldId?.id).change(function () { 1353 runRuleEngine(); 1354 }) 1355 jQuery("#nf-field-" + stateFieldId?.id).change(function () { 1356 runRuleEngine(); 1357 }) 1358 1359 function runRuleEngine() { 1360 console.log('runRuleEngine'); 1361 1362 const values = { 1363 program: jQuery("#nf-field-" + programID).val(), 1364 campus: jQuery("#nf-field-" + campusID).val(), 1365 semester: jQuery("#nf-field-" + semesterID).val(), 1366 country: jQuery("#nf-field-" + countryFieldId?.id)?.val(), 1367 state: jQuery("#nf-field-" + stateFieldId?.id)?.val(), 1368 }; 1369 1370 sessionStorage.setItem("nf_program", values.program || ""); 1371 sessionStorage.setItem("nf_campus", values.campus || ""); 1372 sessionStorage.setItem("nf_semester", values.semester || ""); 1373 sessionStorage.setItem("nf_country", values.country || ""); 1374 sessionStorage.setItem("nf_state", values.state || ""); 1375 1376 const fieldIDs = { 1377 program: programID, 1378 campus: campusID, 1379 semester: semesterID, 1380 country: countryFieldId?.id, 1381 state: stateFieldId?.id 1382 }; 1383 1384 // STEP 1: RESET everything first 1385 resetRuleEffects(fieldIDs); 1386 1387 // STEP 2: Apply rules fresh 1388 processRules(ninjaFormDataJS.rulesData, values, fieldIDs); 1389 } 1390 1391 // If only one Program is available on load, auto-select it. This cascades
1392 // (via the change handlers above) to auto-select Campus and Semester too, 1393 // when each of those also only has a single available option. 1394 autoSelectSingleOption(programID); 1395 1396 runRuleEngine(); // run once on load 1397} 1398 1399// Non-chained dropdown behavior (no school, useDynamic === '') 1400// CAMPUS > PROGRAM > SEMESTER chain (no school, useDynamic === 'campus') - UPDATED 1401function campusProgramSemesterChainNoSchool(programID, programLabelID, campusID, campusLabelID, semesterID, semesterLabelID, countryFieldId, stateFieldId) { 1402 // Initialize campus field with "Choose Campus" on page load 1403 const $campusSelect = jQuery('#nf-field-' + campusID); 1404 $campusSelect.find('option').remove(); 1405 $campusSelect.find('optgroup').remove(); 1406 $campusSelect.append('<option value="" label="">Choose Campus</option>'); 1407 $campusSelect.append(ninjaFormDataJS.campusesHTML); 1408 1409 // Initialize program field with "--" on page load 1410 initializeDropdownWithDefault(programID, '--'); 1411 1412 // Initialize semester field with "--" on page load 1413 initializeDropdownWithDefault(semesterID, '--'); 1414 1415 // Campus -> Program filtering - UPDATED to use program_campus_mapping 1416 jQuery("#nf-field-" + campusID).change(function () { 1417 const campusValue = jQuery(this).val(); 1418 1419 // Reset program dropdown with "Choose Program" after campus selection 1420 initializeDropdownWithDefault(programID, 'Choose Program'); 1421 1422 if (campusValue) { 1423 jQuery("#nf-field-" + programID).append(ninjaFormDataJS.programsHTML); 1424 1425 // Try to filter programs using mapping 1426 const mappingApplied = filterProgramsByCampusMapping(campusValue, programID); 1427 1428 // If no mapping was applied, show all programs 1429 if (!mappingApplied) { 1430 // Filter programs based on selected campus 1431 jQuery("#nf-field-" + programID + " option").each((index, option) => { 1432 let optionCampuses = jQuery(option).attr('data-class'); 1433 if (typeof optionCampuses !== 'undefined' && campusValue !== '') { 1434 optionCampuses = optionCampuses.split(' '); 1435 if (optionCampuses.indexOf(campusValue) === -1 && option.value !== '') { 1436 jQuery(option).remove(); 1437 } 1438 } 1439 }); 1440 } 1441 1442 jQuery("#nf-field-" + programID + " optgroup").each((index, option) => { 1443 if (jQuery(option).is(':empty')) { 1444 jQuery(option).remove(); 1445 } 1446 }); 1447 } 1448 1449 // Reset dependent field (Semester) to "--" 1450 initializeDropdownWithDefault(semesterID, '--'); 1451 1452 const campusLabel = jQuery("#nf-field-" + campusID + " option:selected").text(); 1453 jQuery("#nf-field-" + campusLabelID).val(campusLabel).trigger('change'); 1454 1455 // If only one Program remains available, auto-select it instead of 1456 // requiring the user to choose it manually. 1457 autoSelectSingleOption(programID); 1458 1459 runRuleEngine(); 1460 }); 1461 1462 // Program -> Semester filtering 1463 jQuery("#nf-field-" + programID).change(function () { 1464 const programValue = jQuery(this).val(); 1465 const campusValue = jQuery("#nf-field-" + campusID).val(); 1466 1467 // Reset semester dropdown with "Choose Semester" after program selection 1468 initializeDropdownWithDefault(semesterID, 'Choose Semester'); 1469 1470 if (programValue && campusValue) { 1471 jQuery("#nf-field-" + semesterID).append(ninjaFormDataJS.semestersHTML); 1472 1473 // Filter semesters based on selected program AND campus 1474 jQuery("#nf-field-" + semesterID + " option").each((index, option) => { 1475 let optionSemesters = jQuery(option).attr('data-class'); 1476 let optionPrograms = jQuery(option).attr('data-program-solo-id'); 1477 1478 let shouldRemove = false; 1479 1480 // Filter by campus and program using data-class (format: "program-campus" pairs) 1481 if (typeof optionSemesters !== 'undefined' && campusValue !== '' && programValue !== '') { 1482 const semesterPairs = optionSemesters.split(' '); 1483 let hasMatchingPair = false;
1484 1485 semesterPairs.forEach(pair => { 1486 const [programId, campusId] = pair.split('-'); 1487 if (programId === programValue && campusId === campusValue) { 1488 hasMatchingPair = true; 1489 } 1490 }); 1491 1492 if (!hasMatchingPair && option.value !== '') { 1493 shouldRemove = true; 1494 } 1495 } 1496 1497 // Additional filter by program using data-program-solo-id 1498 if (!shouldRemove && typeof optionPrograms !== 'undefined' && programValue !== '') { 1499 optionPrograms = optionPrograms.split(' '); 1500 if (optionPrograms.indexOf(programValue) === -1 && option.value !== '') { 1501 shouldRemove = true; 1502 } 1503 } 1504 1505 if (shouldRemove) { 1506 jQuery(option).remove(); 1507 } 1508 }); 1509 1510 jQuery("#nf-field-" + semesterID + " optgroup").each((index, option) => { 1511 if (jQuery(option).is(':empty')) { 1512 jQuery(option).remove(); 1513 } 1514 }); 1515 1516 singleSemesterSelect(semesterID, campusID); 1517 } 1518 1519 const programLabel = jQuery("#nf-field-" + programID + " option:selected").text(); 1520 jQuery("#nf-field-" + programLabelID).val(programLabel).trigger('change'); 1521 runRuleEngine(); 1522 }); 1523 1524 // Semester change handler 1525 jQuery("#nf-field-" + semesterID).change(function () { 1526 const semesterLabel = jQuery("#nf-field-" + semesterID + " option:selected").text(); 1527 jQuery("#nf-field-" + semesterLabelID).val(semesterLabel).trigger('change'); 1528 runRuleEngine(); 1529 }); 1530 1531 jQuery("#nf-field-" + countryFieldId?.id).change(function () { 1532 runRuleEngine(); 1533 }) 1534 jQuery("#nf-field-" + stateFieldId?.id).change(function () { 1535 runRuleEngine(); 1536 }) 1537 1538 function runRuleEngine() { 1539 1540 const values = { 1541 program: jQuery("#nf-field-" + programID).val(), 1542 campus: jQuery("#nf-field-" + campusID).val(), 1543 semester: jQuery("#nf-field-" + semesterID).val(), 1544 country: jQuery("#nf-field-" + countryFieldId?.id)?.val(), 1545 state: jQuery("#nf-field-" + stateFieldId?.id)?.val(), 1546 }; 1547 1548 const fieldIDs = { 1549 program: programID, 1550 campus: campusID, 1551 semester: semesterID, 1552 country: countryFieldId?.id, 1553 state: stateFieldId?.id 1554 }; 1555 1556 // STEP 1: RESET everything first 1557 resetRuleEffects(fieldIDs); 1558 1559 // STEP 2: Apply rules fresh 1560 processRules(ninjaFormDataJS.rulesData, values, fieldIDs); 1561 } 1562 1563 // If only one Campus is available on load, auto-select it. This cascades 1564 // (via the change handlers above) to auto-select Program and Semester too, 1565 // when each of those also only has a single available option. 1566 autoSelectSingleOption(campusID); 1567 1568 runRuleEngine(); // run once on load 1569} 1570 1571function programCampusSemesterChain(programID, programLabelID, campusID, campusLabelID, semesterID, semesterLabelID) { 1572 // Initialize dropdowns with defaults 1573 initializeDropdownWithDefault(campusID, 'Choose Campus'); 1574 initializeDropdownWithDefault(semesterID, 'Choose Semester'); 1575 1576 // Program -> Campus filtering - UPDATED to use program_campus_mapping with fallback 1577 programCampusDropDowns(programID, programLabelID, campusID, campusLabelID); 1578 1579 // Campus -> Semester filtering 1580 campusSemesterDropDowns(campusID, campusLabelID, semesterID, semesterLabelID); 1581} 1582 1583// CAMPUS > PROGRAM > SEMESTER chain (no school, useDynamic === 'campus') 1584function campusProgramSemesterChain(programID, programLabelID, campusID, campusLabelID, semesterID, semesterLabelID) { 1585 // Initialize dropdowns with defaults 1586 initializeDropdownWithDefault(programID, 'Choose Program'); 1587 initializeDropdownWithDefault(semesterID, 'Choose Semester'); 1588 1589 // Campus -> Program filtering - UPDATED to use program_campus_mapping 1590 campusProgramDropDowns(programID, programLabelID, campusID, campusLabelID); 1591 1592 // Campus -> Semester filtering 1593 campusSemesterDropDowns(campusID, campusLabelID, semesterID, semesterLabelID); 1594} 1595 1596// UPDATED: Program -> Campus filtering with program_campus_mapping and fallback 1597function programCampusDropDowns(programID, programLabelID, campusID, campusLabelID) { 1598 // Remove any empty data-solo-campus-id options 1599 jQuery(`#nf-field-${programID} option[data-solo-campus-id=""]`).remove(); 1600 1601 jQuery("#nf-field-" + programID).change(() => { 1602 const programValue = jQuery("#nf-field-" + programID).val(); 1603 1604 // Reset campus dropdown with default option 1605 initializeDropdownWithDefault(campusID, 'Choose Campus'); 1606 1607 if (programValue) { 1608 jQuery("#nf-field-" + campusID).append(ninjaFormDataJS.campusesHTML); 1609 1610 // Try to filter using mapping, fallback to data-class if no mapping 1611 filterCampusesByProgramMapping(programValue, campusID, programID); 1612 1613 jQuery("#nf-field-" + campusID + " optgroup").each((index, option) => { 1614 if (jQuery(option).is(':empty')) { 1615 jQuery(option).remove(); 1616 } 1617 }); 1618 } 1619 1620 const programLabel = jQuery("#nf-field-" + programID + " option:selected").text(); 1621 jQuery("#nf-field-" + programLabelID).val(programLabel).trigger('change'); 1622 }); 1623 1624 // Campus change handler for label updates 1625 jQuery("#nf-field-" + campusID).change(() => { 1626 const campusLabel = jQuery("#nf-field-" + campusID + " option:selected").text(); 1627 jQuery("#nf-field-" + campusLabelID).val(campusLabel).trigger('change'); 1628 }); 1629} 1630 1631// UPDATED: Campus -> Program filtering with program_campus_mapping 1632function campusProgramDropDowns(programID, programLabelID, campusID, campusLabelID) { 1633 jQuery("#nf-field-" + campusID).change(() => { 1634 const campusValue = jQuery("#nf-field-" + campusID).val(); 1635 1636 // Reset program dropdown with default option 1637 initializeDropdownWithDefault(programID, 'Choose Program'); 1638 1639 if (campusValue) { 1640 jQuery("#nf-field-" + programID).append(ninjaFormDataJS.programsHTML); 1641 1642 // Try to filter programs using mapping 1643 const mappingApplied = filterProgramsByCampusMapping(campusValue, programID); 1644 1645 // If no mapping was applied, show all programs 1646 if (!mappingApplied) { 1647 jQuery("#nf-field-" + programID + " option").show(); 1648 jQuery("#nf-field-" + programID + " option").prop('disabled', false); 1649 } 1650 1651 jQuery("#nf-field-" + programID + " optgroup").each((index, option) => { 1652 if (jQuery(option).is(':empty')) { 1653 jQuery(option).remove(); 1654 } 1655 }); 1656 } 1657 1658 const campusLabel = jQuery("#nf-field-" + campusID + " option:selected").text(); 1659 jQuery("#nf-field-" + campusLabelID).val(campusLabel).trigger('change'); 1660 }); 1661 1662 jQuery("#nf-field-" + programID).change(() => { 1663 const programLabel = jQuery("#nf-field-" + programID + " option:selected").text(); 1664 jQuery("#nf-field-" + programLabelID).val(programLabel).trigger('change'); 1665 }); 1666} 1667 1668// UPDATED FUNCTION: Campus -> Semester filtering (standalone) 1669function campusSemesterDropDowns(campusID, campusLabelID, semesterID, semesterLabelID) { 1670 jQuery("#nf-field-" + campusID).change(function () { 1671 const campusValue = jQuery(this).val(); 1672 1673 // Reset and populate semester dropdown with default 1674 initializeDropdownWithDefault(semesterID, 'Choose Semester'); 1675 1676 if (campusValue) { 1677 jQuery("#nf-field-" + semesterID).append(ninjaFormDataJS.semestersHTML); 1678 1679 // Filter semesters based on selected campus 1680 jQuery("#nf-field-" + semesterID + " option").each((index, option) => { 1681 let optionSemesters = jQuery(option).attr('data-class'); 1682 1683 if (typeof optionSemesters !== 'undefined' && campusValue !== '') { 1684 optionSemesters = optionSemesters.split(' '); 1685 if (optionSemesters.indexOf(campusValue) === -1 && option.value !== '') { 1686 jQuery(option).remove(); 1687 } 1688 } 1689 }); 1690 1691 jQuery("#nf-field-" + semesterID + " optgroup").each((index, option) => { 1692 if (jQuery(option).is(':empty')) { 1693 jQuery(option).remove(); 1694 } 1695 }); 1696 1697 singleSemesterSelect(semesterID, campusID); 1698 } 1699 1700 const campusLabel = jQuery("#nf-field-" + campusID + " option:selected").text(); 1701 jQuery("#nf-field-" + campusLabelID).val(campusLabel).trigger('change'); 1702 }); 1703 1704 // Semester change handler 1705 jQuery("#nf-field-" + semesterID).change(function () { 1706 const semesterLabel = jQuery("#nf-field-" + semesterID + " option:selected").text(); 1707 jQuery("#nf-field-" + semesterLabelID).val(semesterLabel).trigger('change'); 1708 }); 1709} 1710 1711// Add the missing selector functions 1712function getUtmSchoolField(form) { 1713 return form.fields.find(f => f.key === 'utm_school'); 1714} 1715 1716// Add the missing school initialization function for standalone school fields 1717function initializeSchoolField(schoolID, schoolLabelID) { 1718 jQuery("#nf-field-" + schoolID).change(function () { 1719 const schoolLabel = jQuery("#nf-field-" + schoolID + " option:selected").text(); 1720 jQuery("#nf-field-" + schoolLabelID).val(schoolLabel).trigger('change'); 1721 }); 1722 1723 // Set initial value if there's a selected option 1724 const selectedSchool = jQuery("#nf-field-" + schoolID + " option:selected"); 1725 if (selectedSchool.length > 0 && selectedSchool.val() !== '') { 1726 const initialSchoolLabel = selectedSchool.text(); 1727 jQuery("#nf-field-" + schoolLabelID).val(initialSchoolLabel).trigger('change'); 1728 } 1729} 1730 1731// Add the missing UTM function for schools 1732function selectSchoolUTM(schoolID, schoolLabelID, utmSchool) { 1733 if (utmSchool && utmSchool.value) { 1734 jQuery('#nf-field-' + schoolID).val(utmSchool.value).trigger('change'); 1735 const schoolLabel = jQuery("#nf-field-" + schoolID + " option:selected").text(); 1736 jQuery("#nf-field-" + schoolLabelID).val(schoolLabel).trigger('change'); 1737 } 1738} 1739 1740// UPDATED FUNCTION: Directly load school options 1741function loadSchoolOptions(schoolID, schoolLabelID) { 1742 1743 const $schoolSelect = jQuery('#nf-field-' + schoolID); 1744 if (!$schoolSelect.length) { 1745 console.error('School select element not found: #nf-field-' + schoolID); 1746 return; 1747 } 1748 1749 // Clear existing options 1750 $schoolSelect.find('option').remove(); 1751 $schoolSelect.find('optgroup').remove(); 1752 1753 // Add default option first 1754 $schoolSelect.append('<option value="" label="">Choose School</option>'); 1755 1756 // Append the schoolsHTML that's now available from PHP 1757 if (typeof ninjaFormDataJS.schoolsHTML !== 'undefined' && ninjaFormDataJS.schoolsHTML) { 1758 $schoolSelect.append(ninjaFormDataJS.schoolsHTML); 1759 } else { 1760 console.warn('ninjaFormDataJS.schoolsHTML is undefined'); 1761 } 1762 1763 // Initialize the change handler 1764 initializeSchoolField(schoolID, schoolLabelID); 1765} 1766 1767// Add non-chained behavior for school forms 1768// Non-chained dropdown behavior for school forms (useDynamic === '') 1769function nonChainedSchoolDropDowns(schoolID, schoolLabelID, programID, programLabelID, campusID, campusLabelID, semesterID, semesterLabelID, countryFieldId, stateFieldId) { 1770 // Initialize all dropdowns with their full options including defaults 1771 loadSchoolOptions(schoolID, schoolLabelID); // This now includes "Choose School" 1772 1773 // Initialize program field with all options 1774 const $programSelect = jQuery('#nf-field-' + programID); 1775 $programSelect.find('option').remove(); 1776 $programSelect.find('optgroup').remove(); 1777 $programSelect.append('<option value="" label="">Choose Program</option>'); 1778 $programSelect.append(ninjaFormDataJS.programsHTML); 1779 1780 // Initialize campus field with all options 1781 const $campusSelect = jQuery('#nf-field-' + campusID); 1782 $campusSelect.find('option').remove(); 1783 $campusSelect.find('optgroup').remove(); 1784 $campusSelect.append('<option value="" label="">Choose Campus</option>'); 1785 $campusSelect.append(ninjaFormDataJS.campusesHTML); 1786 1787 // Initialize semester field with all options 1788 const $semesterSelect = jQuery('#nf-field-' + semesterID); 1789 $semesterSelect.find('option').remove(); 1790 $semesterSelect.find('optgroup').remove(); 1791 $semesterSelect.append('<option value="" label="">Choose Semester</option>'); 1792 $semesterSelect.append(ninjaFormDataJS.semestersHTML); 1793
1794 // Set up change handlers for labels only (no filtering) 1795 jQuery("#nf-field-" + schoolID).change(function () { 1796 const schoolLabel = jQuery("#nf-field-" + schoolID + " option:selected").text(); 1797 jQuery("#nf-field-" + schoolLabelID).val(schoolLabel).trigger('change'); 1798 runRuleEngine(); 1799 }); 1800 1801 jQuery("#nf-field-" + programID).change(function () { 1802 const programLabel = jQuery("#nf-field-" + programID + " option:selected").text(); 1803 jQuery("#nf-field-" + programLabelID).val(programLabel).trigger('change'); 1804 runRuleEngine(); 1805 }); 1806 1807 jQuery("#nf-field-" + campusID).change(function () { 1808 const campusLabel = jQuery("#nf-field-" + campusID + " option:selected").text(); 1809 jQuery("#nf-field-" + campusLabelID).val(campusLabel).trigger('change'); 1810 runRuleEngine(); 1811 }); 1812 1813 jQuery("#nf-field-" + semesterID).change(function () { 1814 const semesterLabel = jQuery("#nf-field-" + semesterID + " option:selected").text(); 1815 jQuery("#nf-field-" + semesterLabelID).val(semesterLabel).trigger('change'); 1816 runRuleEngine(); 1817 }); 1818 1819 jQuery("#nf-field-" + countryFieldId?.id).change(function () { 1820 runRuleEngine(); 1821 }) 1822 jQuery("#nf-field-" + stateFieldId?.id).change(function () { 1823 runRuleEngine(); 1824 }) 1825 1826 function runRuleEngine() { 1827 1828 const values = { 1829 school: jQuery("#nf-field-" + schoolID).val(), 1830 program: jQuery("#nf-field-" + programID).val(), 1831 campus: jQuery("#nf-field-" + campusID).val(), 1832 semester: jQuery("#nf-field-" + semesterID).val(), 1833 country: jQuery("#nf-field-" + countryFieldId?.id)?.val(), 1834 state: jQuery("#nf-field-" + stateFieldId?.id)?.val(), 1835 }; 1836 1837 const fieldIDs = { 1838 school: schoolID, 1839 program: programID, 1840 campus: campusID, 1841 semester: semesterID, 1842 country: countryFieldId?.id, 1843 state: stateFieldId?.id 1844 }; 1845 1846 // STEP 1: RESET everything first 1847 resetRuleEffects(fieldIDs); 1848 1849 // STEP 2: Apply rules fresh 1850 processRules(ninjaFormDataJS.rulesData, values, fieldIDs); 1851 } 1852 1853 // Since Program, Campus, and Semester are independent (non-chained) here, 1854 // auto-select each one on load if it only has a single available option. 1855 autoSelectSingleOption(programID); 1856 autoSelectSingleOption(campusID); 1857 autoSelectSingleOption(semesterID); 1858 1859 runRuleEngine(); // run once on load 1860} 1861 1862// Complete school > program > campus > semester chaining function 1863function schoolProgramCampusDropDowns(schoolID, schoolLabelID, programID, programLabelID, campusID, campusLabelID, semesterID, semesterLabelID) { 1864 // Initialize school field 1865 initializeSchoolField(schoolID, schoolLabelID); 1866 1867 // When school changes, filter programs 1868 jQuery("#nf-field-" + schoolID).change(function () { 1869 const schoolValue = jQuery(this).val(); 1870 1871 // Reset and populate program dropdown 1872 jQuery("#nf-field-" + programID + " option").remove(); 1873 jQuery("#nf-field-" + programID + " optgroup").remove(); 1874 jQuery("#nf-field-" + programID).append('<option value="" label="">Choose Program</option>'); 1875 jQuery("#nf-field-" + programID).append(ninjaFormDataJS.programsHTML); 1876 1877 // Filter programs based on selected school 1878 jQuery("#nf-field-" + programID + " option").each((index, option) => { 1879 let optionSchools = jQuery(option).attr('data-schools'); 1880 if (typeof optionSchools !== 'undefined' && schoolValue !== '') { 1881 optionSchools = optionSchools.split(' '); 1882 if (optionSchools.indexOf(schoolValue) === -1 && option.value !== '') { 1883 jQuery(option).remove(); 1884 } 1885 } 1886 }); 1887 1888 jQuery("#nf-field-" + programID + " optgroup").each((index, option) => { 1889 if (jQuery(option).is(':empty')) { 1890 jQuery(option).remove(); 1891 } 1892 }); 1893 1894 // Trigger program change to filter campuses 1895 jQuery("#nf-field-" + programID).trigger('change'); 1896 1897 const schoolLabel = jQuery("#nf-field-" + schoolID + " option:selected").text(); 1898 jQuery("#nf-field-" + schoolLabelID).val(schoolLabel).trigger('change'); 1899 }); 1900 1901 // Program change handler - UPDATED to use program_campus_mapping with fallback 1902 jQuery("#nf-field-" + programID).change(function () { 1903 const programValue = jQuery(this).val(); 1904 1905 // Reset and populate campus dropdown 1906 jQuery("#nf-field-" + campusID + " option").remove(); 1907 jQuery("#nf-field-" + campusID + " optgroup").remove(); 1908 jQuery("#nf-field-" + campusID).append('<option value="" label="">Choose Campus</option>'); 1909 jQuery("#nf-field-" + campusID).append(ninjaFormDataJS.campusesHTML); 1910 1911 if (programValue) { 1912 // Try to filter using mapping, fallback to data-class if no mapping 1913 filterCampusesByProgramMapping(programValue, campusID, programID); 1914 } 1915 1916 jQuery("#nf-field-" + campusID + " optgroup").each((index, option) => { 1917 if (jQuery(option).is(':empty')) { 1918 jQuery(option).remove(); 1919 } 1920 }); 1921 1922 const programLabel = jQuery("#nf-field-" + programID + " option:selected").text(); 1923 jQuery("#nf-field-" + programLabelID).val(programLabel).trigger('change'); 1924 1925 // Trigger campus change to filter semesters 1926 jQuery("#nf-field-" + campusID).trigger('change'); 1927 }); 1928 1929 // Campus change handler 1930 jQuery("#nf-field-" + campusID).change(function () { 1931 const campusValue = jQuery(this).val(); 1932 1933 // Reset and populate semester dropdown 1934 jQuery("#nf-field-" + semesterID + " option").remove(); 1935 jQuery("#nf-field-" + semesterID + " optgroup").remove(); 1936 jQuery("#nf-field-" + semesterID).append('<option value="" label="">Choose Semester</option>'); 1937 jQuery("#nf-field-" + semesterID).append(ninjaFormDataJS.semestersHTML); 1938 1939 // Filter semesters based on selected campus 1940 jQuery("#nf-field-" + semesterID + " option").each((index, option) => { 1941 let optionSemesters = jQuery(option).attr('data-class'); 1942 1943 if (typeof optionSemesters !== 'undefined' && campusValue !== '') { 1944 optionSemesters = optionSemesters.split(' '); 1945 if (optionSemesters.indexOf(campusValue) === -1 && option.value !== '') { 1946 jQuery(option).remove(); 1947 } 1948 } 1949 }); 1950 1951 jQuery("#nf-field-" + semesterID + " optgroup").each((index, option) => { 1952 if (jQuery(option).is(':empty')) { 1953 jQuery(option).remove(); 1954 } 1955 }); 1956 1957 const campusLabel = jQuery("#nf-field-" + campusID + " option:selected").text(); 1958 jQuery("#nf-field-" + campusLabelID).val(campusLabel).trigger('change'); 1959 1960 singleSemesterSelect(semesterID, campusID); 1961 }); 1962 1963 // Semester change handler 1964 jQuery("#nf-field-" + semesterID).change(function () { 1965 const semesterLabel = jQuery("#nf-field-" + semesterID + " option:selected").text(); 1966 jQuery("#nf-field-" + semesterLabelID).val(semesterLabel).trigger('change'); 1967 }); 1968} 1969 1970//selector functions 1971 1972function getPageUrlField(form) { 1973 return form.fields.find(f => f.key === 'page_url'); 1974} 1975 1976function getThankYouPageField(form) { 1977 return form.fields.find(f => f.key === 'thank_you_page'); 1978} 1979 1980function getSubmitButton(form) { 1981 return form.fields.find(f => f.key.includes('submit')); 1982} 1983 1984function getTimezoneField(form) { 1985 return form.fields.find(f => f.key === 'selected_timezone'); 1986} 1987 1988function getProgramSelectField(form) { 1989 return form.fields.find(f => f.key === 'program_select'); 1990} 1991 1992function getCampusSelectField(form) { 1993 return form.fields.find(f => f.key === 'campus_select'); 1994} 1995 1996function getCampusDataField(form) { 1997 return form.fields.find(f => f.key === 'use_campus_data_attribute'); 1998} 1999 2000function getUtmCampusField(form) { 2001 return form.fields.find(f => f.key === 'utm_campus'); 2002} 2003 2004// School Field Functions 2005function getSchoolSelectField(form) { 2006 return form.fields.find(f => f.key === 'school_select'); 2007} 2008 2009function getSchoolLabelField(form) { 2010 return form.fields.find(f => f.key === 'school_label'); 2011} 2012 2013function getCampusLabelField(form) { 2014 return form.fields.find(f => f.key === 'campus_label'); 2015} 2016 2017function getUtmProgramField(form) { 2018 return form.fields.find(f => f.key === 'utm_program'); 2019} 2020 2021function getProgramLabelField(form) { 2022 return form.fields.find(f => f.key === 'program_label'); 2023} 2024 2025function getSemesterSelectField(form) { 2026 return form.fields.find(f => f.key === 'semester_select'); 2027} 2028 2029function getSemesterLabelField(form) { 2030 return form.fields.find(f => f.key === 'semester_label'); 2031} 2032 2033function getSrcParamTranslatedField(form) { 2034 return form.fields.find(f => f.key === 'src_parameter_translated'); 2035} 2036 2037function getCountryZipTypeField(form) { 2038 return form.fields.find(f => f.key === 'country_zip_type'); 2039} 2040 2041function getZipStatusField(form) { 2042 return form.fields.find(f => f.key === 'zip_status'); 2043} 2044 2045function getPostalCodeField(form) { 2046 return form.fields.find(f => f.key === 'postal_code'); 2047} 2048 2049function getZipCodeField(form) { 2050 return form.fields.find(f => f.key === 'zip_code'); 2051} 2052 2053function getCountryField(form) { 2054 return form.fields.find(f => f.key === 'list_country_main'); 2055} 2056 2057function getCityField(form) { 2058 return form.fields.find(f => f.key === 'visible_city'); 2059} 2060 2061function getStateField(form) { 2062 return form.fields.find(f => f.key === 'visible_state'); 2063} 2064 2065function getProvinceField(form) { 2066 return form.fields.find(f => f.key === 'province_ca'); 2067} 2068 2069function getInternationalStateField(form) { 2070 return form.fields.find(f => f.key === 'int_state'); 2071} 2072 2073function getLocalLatField(form) { 2074 return form.fields.find(f => f.key === 'local_latitude'); 2075} 2076 2077function getLocalLongField(form) { 2078 return form.fields.find(f => f.key === 'local_longitude'); 2079} 2080 2081function getCountryCodeField(form) { 2082 return form.fields.find(f =>
2082 f.key === 'gdpr_country_code'); 2083} 2084 2085function getEmailField(form) { 2086 return form.fields.find(f => f.type === 'email'); 2087} 2088 2089function getDateLimit(form) { 2090 const i = ninjaFormDataJS.ninjaFormsIDs.indexOf(form.id); 2091 if (ninjaFormDataJS.ninjaDateSettings[i] > 0) return Number(ninjaFormDataJS.ninjaDateSettings[i]); 2092 else return 0; 2093} 2094 2095// Looks up this form's per-form "Data Sync" setting (Campus First / Program 2096// First / None), configured under DataFlow Sync > General > Campus-Program 2097// Chaining and localized as ninjaFormDataJS.formCampusSettings, keyed to 2098// ninjaFormDataJS.ninjaFormsIDs by form ID. 2099function getFormDataSyncSetting(form) { 2100 const i = ninjaFormDataJS.ninjaFormsIDs.indexOf(form.id); 2101 return i > -1 ? ninjaFormDataJS.formCampusSettings[i] : undefined; 2102} 2103 2104function useDynamicCampusProgram(form) { 2105 const dataSyncSetting = getFormDataSyncSetting(form); // 'campus' | 'program' | 'no' | undefined 2106 2107 // Page-level override: the "Form is Program First?" checkbox in the 2108 // Page Editor (TCS Field Config metabox) FORCIBLY wins over the Data 2109 // Sync setting whenever it's checked -- e.g. Data Sync says 2110 // "Campus First" (or even "None") but this page has the checkbox 2111 // checked -> use Program First regardless. 2112 const pageConfig = ninjaFormDataJS.pageFieldConfig; 2113 if (pageConfig && pageConfig.program_first) { 2114 return 'program'; 2115 } 2116 2117 // Data Sync explicitly says this form uses no campus/program chaining at 2118 // all, and the checkbox above didn't override it -- no chaining. 2119 if (dataSyncSetting === 'no') { 2120 return false; 2121 } 2122 2123 // No page-level override -- default to whatever Data Sync says for this form. 2124 if (dataSyncSetting === 'campus') return 'campus'; 2125 if (dataSyncSetting === 'program') return 'program'; 2126 2127 // No Data Sync entry configured for this form -- fall back to the 2128 // Program-first progressive-filtering chain -- Program shows every 2129 // option, Campus and Semester stay empty until a Program is chosen. 2130 return 'program'; 2131} 2132 2133//custom NinjaForm features 2134function captureURL(pageUrlId) { 2135 jQuery('#nf-field-' + pageUrlId).val(window.location.href).trigger('change'); 2136} 2137 2138function setThankYouPage(thankYouPageId) { 2139 if (ninjaFormDataJS.thankYouPage !== 'no') { 2140 jQuery('#nf-field-' + thankYouPageId).val(ninjaFormDataJS.thankYouPage).trigger('change'); 2141 } 2142} 2143 2144function getLatLong(localLatitudeId, localLongitudeId, submitButtonId) { 2145 jQuery(`#nf-field-${submitButtonId}`).prop('disabled', true); 2146 jQuery.ajax({ 2147 url: `https://www.googleapis.com/geolocation/v1/geolocate?key=${ninjaFormDataJS.googleAPIKey}`, 2148 cache: false, 2149 dataType: "json", 2150 type: "POST", 2151 complete: () => { 2152 jQuery(`#nf-field-${submitButtonId}`).prop('disabled', false); 2153 }, 2154 success: result => { 2155 const lat = result.location.lat; 2156 const lng = result.location.lng; 2157 jQuery(`#nf-field-${localLatitudeId}`).val(lat).trigger('change'); 2158 jQuery(`#nf-field-${localLongitudeId}`).val(lng).trigger('change'); 2159 }, 2160 error: err => { console.log(err); } 2161 }); 2162} 2163 2164function gdprRequirements(countryCodeID) { 2165 jQuery.ajax({ 2166 url: `https://www.googleapis.com/geolocation/v1/geolocate?key=${ninjaFormDataJS.googleAPIKey}`, 2167 cache: false, 2168 dataType: 'json', 2169 type: 'POST', 2170 success: result => { 2171 const lat = result.location.lat; 2172 const lng = result.location.lng; 2173 jQuery.ajax({ 2174 url: `https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${lng}&language=en&key=${ninjaFormDataJS.googleAPIKey}`, 2175 cache: false, 2176 dataType: 'json', 2177 type: 'GET', 2178 success: result => { 2179 const countryCode = result.results.find(x => x.types.indexOf('country') >= 0).address_components[0].short_name.toUpperCase(); 2180 jQuery(`#nf-field-${countryCodeID}`).val(countryCode).trigger('change'); 2181 }, 2182 error: err => { console.log(err); } 2183 }); 2184 }, 2185 error: err => { console.log(err); } 2186 }); 2187} 2188 2189function validateZipCode(countryFieldID, countryZipTypeID, zipFieldID, postalFieldID, stateFieldID, provFieldID, intStateFieldID, countryZipStatusID, cityFieldID, submitButtonID) { 2190 const zipCodeTimeZoneValidationController = Marionette.Object.extend({ 2191 initialize: function () { 2192 const fieldsChannel = Backbone.Radio.channel('fields'); 2193 this.listenTo(fieldsChannel, 'change:modelValue', this.validate); 2194 }, 2195 validate: model => { 2196 if (model.get('type') !== 'zip' && model.get('type') !== 'listcountry') return; 2197 const fieldValue = model.get('value'); 2198 const countriesNoPostal = [{ 2199 "name": "Angola", 2200 "code": "AO" 2201 }, { 2202 "name": "Antigua and Barbuda", 2203 "code": "AG" 2204 }, { 2205 "name": "Aruba", 2206 "code": "AW" 2207 }, { 2208 "name": "Bahamas", 2209 "code": "BS" 2210 }, { 2211 "name": "Belize", 2212 "code": "BZ" 2213 }, { 2214 "name": "Benin", 2215 "code": "BJ" 2216 }, { 2217 "name": "Botswana", 2218 "code": "BW" 2219 }, { 2220 "name": "Burkina Faso", 2221 "code": "BF" 2222 }, { 2223 "name": "Burundi", 2224 "code": "BI"
2225 }, { 2226 "name": "Cameroon", 2227 "code": "CM" 2228 }, { 2229 "name": "Central African Republic", 2230 "code": "CF" 2231 }, { 2232 "name": "Comoros", 2233 "code": "KM" 2234 }, { 2235 "name": "Congo", 2236 "code": "CG" 2237 }, { 2238 "name": "Congo, the Democratic Republic of the", 2239 "code": "CD" 2240 }, { 2241 "name": "Cook Islands", 2242 "code": "CK" 2243 }, { 2244 "name": "Cote d'Ivoire", 2245 "code": "CI" 2246 }, { 2247 "name": "Djibouti", 2248 "code": "DJ" 2249 }, { 2250 "name": "Dominica", 2251 "code": "DM" 2252 }, { 2253 "name": "Equatorial Guinea", 2254 "code": "GQ" 2255 }, { 2256 "name": "Eritrea", 2257 "code": "ER" 2258 }, { 2259 "name": "Fiji", 2260 "code": "FJ" 2261 }, { 2262 "name": "French Southern Territories", 2263 "code": "TF" 2264 }, { 2265 "name": "Gambia", 2266 "code": "GM" 2267 }, { 2268 "name": "Ghana", 2269 "code": "GH" 2270 }, { 2271 "name": "Grenada", 2272 "code": "GD" 2273 }, { 2274 "name": "Guinea", 2275 "code": "GN" 2276 }, { 2277 "name": "Guyana", 2278 "code": "GY" 2279 }, { 2280 "name": "Hong Kong", 2281 "code": "HK" 2282 }, { 2283 "name": "Ireland", 2284 "code": "IE" 2285 }, { 2286 "name": "Israel", 2287 "code": "IL" 2288 }, { 2289 "name": "Jamaica", 2290 "code": "JM" 2291 }, { 2292 "name": "Kenya", 2293 "code": "KE" 2294 }, { 2295 "name": "Kiribati", 2296 "code": "KI" 2297 }, { 2298 "name": "Macao", 2299 "code": "MO" 2300 }, { 2301 "name": "Malawi", 2302 "code": "MW" 2303 }, { 2304 "name": "Mali", 2305 "code": "ML" 2306 }, { 2307 "name": "Mauritania", 2308 "code": "MR" 2309 }, { 2310 "name": "Mauritius", 2311 "code": "MU" 2312 }, { 2313 "name": "Montserrat", 2314 "code": "MS" 2315 }, { 2316 "name": "Nauru", 2317 "code": "NR" 2318 }, { 2319 "name": "Netherlands Antilles", 2320 "code": "AN" 2321 }, { 2322 "name": "Nigeria", 2323 "code": "NG" 2324 }, { 2325 "name": "Niue", 2326 "code": "NU" 2327 }, { 2328 "name": "North Korea", 2329 "code": "KP" 2330 }, { 2331 "name": "Panama", 2332 "code": "PA" 2333 }, { 2334 "name": "Qatar", 2335 "code": "QA" 2336 }, { 2337 "name": "Rwanda", 2338 "code": "RW" 2339 }, { 2340 "name": "Saint Kitts and Nevis", 2341 "code": "KN" 2342 }, { 2343 "name": "Saint Lucia", 2344 "code": "LC" 2345 }, { 2346 "name": "Sao Tome and Principe", 2347 "code": "ST" 2348 }, { 2349 "name": "Saudi Arabia", 2350 "code": "SA" 2351 }, { 2352 "name": "Seychelles", 2353 "code": "SC" 2354 }, { 2355 "name": "Sierra Leone", 2356 "code": "SL" 2357 }, { 2358 "name": "Solomon Islands", 2359 "code": "SB" 2360 }, { 2361 "name": "Somalia", 2362 "code": "SO" 2363 }, { 2364 "name": "South Africa", 2365 "code": "ZA" 2366 }, { 2367 "name": "Suriname", 2368 "code": "SR" 2369 }, { 2370 "name": "Syria", 2371 "code": "SY" 2372 }, { 2373 "name": "Tanzania, United Republic of", 2374 "code": "TZ" 2375 }, { 2376 "name": "Timor-Leste", 2377 "code": "TL" 2378 }, { 2379 "name": "Tokelau", 2380 "code": "TK" 2381 }, { 2382 "name": "Tonga", 2383 "code": "TO" 2384 }, { 2385 "name": "Trinidad and Tobago", 2386 "code": "TT" 2387 }, { 2388 "name": "Tuvalu", 2389 "code": "TV" 2390 }, { 2391 "name": "Uganda", 2392 "code": "UG" 2393 }, { 2394 "name": "United Arab Emirates", 2395 "code": "AE" 2396 }, { 2397 "name": "Vanuatu", 2398 "code": "VU" 2399 }, { 2400 "name": "Yemen", 2401 "code": "YE" 2402 }, { 2403 "name": "Zimbabwe", 2404 "code": "ZW" 2405 }]; 2406 if (model.get('type') === 'listcountry') { 2407 jQuery('#nf-field-' + countryZipStatusID).val("ready").trigger('change'); 2408 jQuery('#nf-field-' + postalFieldID).val(""); 2409 jQuery('#nf-field-' + zipFieldID).val(""); 2410 jQuery('#nf-field-' + intStateFieldID).val("").trigger('change'); 2411 jQuery('#nf-field-' + cityFieldID).val(""); 2412 Backbone.Radio.channel('fields').request('remove:error', postalFieldID, 'required-error'); 2413 if (fieldValue === "US") { 2414 jQuery('#nf-field-' + countryZipTypeID).val("us").trigger('change'); 2415 } else if (fieldValue === "CA") { 2416 jQuery('#nf-field-' + countryZipTypeID).val("ca").trigger('change'); 2417 } else { 2418 const countryPostalResult = jQuery.map(countriesNoPostal, val => { 2419 return val.code === fieldValue ? 'nopost' : null; 2420 }); 2421 if (countryPostalResult[0] === 'nopost') { 2422 jQuery('#nf-field-' + countryZipTypeID).val("nopost").trigger('change'); 2423 jQuery('#nf-field-' + countryZipStatusID).val("filled").trigger('change'); 2424 jQuery("label[for=nf-field-" + postalFieldID + "]").html('Postal Code'); 2425 jQuery('#nf-field-' + cityFieldID).val(""); 2426 jQuery('#nf-field-' + intStateFieldID).val("").trigger('change'); 2427 Backbone.Radio.channel('fields').request('remove:error', postalFieldID, 'required-error'); 2428 Backbone.Radio.channel('fields').request('remove:error', zipFieldID, 'required-error'); 2429 } else { 2430 jQuery('#nf-field-' + countryZipTypeID).val("int").trigger('change'); 2431 jQuery("label[for=nf-field-" + postalFieldID + "]").html('Postal Code <span class="ninja-forms-req-symbol">*</span>'); 2432 } 2433 } 2434 } 2435 if (model.get('type') === 'zip') { 2436 if (!model.get('value')) return; 2437 console.log('ZIP VALIDATION START - zipValue:', model.get('value'), 'countryZipType:', jQuery('#nf-field-' + countryZipTypeID).val()); 2438 if (jQuery('#nf-field-' + countryZipTypeID).val() === 'nopost') { 2439 jQuery('#nf-field-' + countryZipStatusID).val("filled").trigger('change'); 2440 } else { 2441 jQuery('#nf-field-' + zipFieldID).parent().append('<img src="' + ninjaFormDataJS.loadingGifPath + '" alt="tcspp-loader" class="zip-loader" width="118" height="15" />'); 2442 jQuery('#nf-field-' + zipFieldID).prop('disabled', true); 2443 jQuery('#nf-field-' + submitButtonID).prop('disabled', true); 2444 const country = jQuery(`#nf-field-${countryFieldID}`).find(':selected').text(); 2445 const apiKey = ninjaFormDataJS.googleAPIKey || 'AIzaSyBadUrU21eiyl9PJ0I7VovULzAPwlL7FUo'; 2446 const searchAddress = model.get('value') + ' ' + country; 2447 const apiUrl = "https://maps.googleapis.com/ma
2447ps/api/geocode/json?address=" + encodeURIComponent(searchAddress) + '&language=en' + "&key=" + apiKey; 2448 2449 console.log('ZIP API CALL:', { 2450 zipValue: model.get('value'), 2451 country: country, 2452 searchAddress: searchAddress, 2453 hasAPIKey: !!apiKey, 2454 apiKeyLength: apiKey ? apiKey.length : 0, 2455 url: apiUrl.substring(0, 100) + '...' 2456 }); 2457 2458 jQuery.ajax({ 2459 url: apiUrl, 2460 cache: false, 2461 dataType: "json", 2462 type: "GET", 2463 complete: () => { 2464 console.log('ZIP AJAX COMPLETE'); 2465 jQuery('.zip-loader').remove(); 2466 jQuery('#nf-field-' + zipFieldID).prop('disabled', false); 2467 jQuery('#nf-field-' + submitButtonID).prop('disabled', false); 2468 }, 2469 success: result => { 2470 console.log('ZIP AJAX SUCCESS - results length:', result.results.length); 2471 console.log('ZIP AJAX FULL RESPONSE:', result); 2472 2473 // Handle API key restriction errors - but validate format first 2474 if (result.status === 'REQUEST_DENIED') { 2475 console.log('ZIP - API Request Denied (likely referer restriction). Validating format...'); 2476 2477 // Basic format validation for common zip codes 2478 const zipValue = model.get('value'); 2479 const countryZipType = jQuery('#nf-field-' + countryZipTypeID).val(); 2480 let isValidFormat = false; 2481 2482 if (countryZipType === 'us') { 2483 // US: 5 digits (12345) or 5+4 (12345-6789) 2484 isValidFormat = /^\d{5}(-\d{4})?$/.test(zipValue); 2485 } else if (countryZipType === 'ca') { 2486 // Canada: A1A 1A1 format (letter-digit-letter space digit-letter-digit) 2487 isValidFormat = /^[A-Z]\d[A-Z]\s?\d[A-Z]\d$/i.test(zipValue); 2488 } else if (countryZipType === 'int') { 2489 // International: at least 3 characters, alphanumeric 2490 isValidFormat = /^[A-Z0-9\s\-]{3,}$/i.test(zipValue); 2491 } 2492 2493 if (isValidFormat) { 2494 console.log('ZIP - Format valid. Accepting zip code despite API restriction.'); 2495 Backbone.Radio.channel('fields').request('remove:error', model.get('id'), 'zip-code-error'); 2496 jQuery('#nf-field-' + countryZipStatusID).val("filled").trigger('change'); 2497 } else { 2498 console.log('ZIP - Format invalid for type:', countryZipType, 'value:', zipValue); 2499 Backbone.Radio.channel('fields').request('add:error', model.get('id'), 'zip-code-error', 'Please Enter A Valid Zip Code'); 2500 jQuery('#nf-field-' + countryZipStatusID).val("ready").trigger('change'); 2501 } 2502 return; 2503 } 2504 2505 if (typeof jQuery('#nf-field-' + countryFieldID).val() === 'undefined') return; 2506 if (result.results.length > 0) { 2507 const address = result.results.find(r => typeof r.address_components.find(ac => typeof ac.types.find(t => t === 'postal_code') !== 'undefined') !== 'undefined'); 2508 2509 console.log('ZIP - Found address with postal code:', typeof address !== 'undefined'); 2510 if (typeof address !== 'undefined') { 2511 console.log('ZIP - Address components:', address.address_components.map(c => ({types: c.types, short_name: c.short_name}))); 2512 2513 if (address.address_components.find(ac => typeof ac.types.find(t => t === 'country') !== 'undefined').short_name === jQuery('#nf-field-' + countryFieldID).val()) { 2514 Backbone.Radio.channel('fields').request('remove:error', model.get('id'), 'zip-code-error', 'Please Enter A Valid Zip Code'); 2515 jQuery('#nf-field-' + countryZipStatusID).val("filled").trigger('change'); 2516 if (address.address_components.find(ac => typeof ac.types.find(t => t === 'country') !== 'undefined').short_name === 'CA' || address.address_components.find(ac => typeof ac.types.find(t => t === 'country') !== 'undefined').short_name === 'US') { 2517 // Find components by type, not by index 2518 const localityComponent = address.address_components.find(ac => ac.types.includes('locality')); 2519 const stateComponent = address.address_components.find(ac => ac.types.includes('administrative_area_level_1')); 2520 2521 console.log('ZIP - Found city:', localityComponent?.short_name, 'state:', stateComponent?.short_name); 2522 2523 if (jQuery('#nf-field-' + countryZipTypeID).val() === 'us') { 2524 if (localityComponent) jQuery('#nf-field-' + cityFieldID).val(localityComponent.short_name).trigger('change'); 2525 if (stateComponent) jQuery('#nf-field-' + stateFieldID).val(stateComponent.short_name).trigger('change'); 2526 } 2527 if (jQuery('#nf-field-' + countryZipTypeID).val() === 'ca') { 2528 if (localityComponent) jQuery('#nf-field-' + cityFieldID).val(localityComponent.short_name).trigger('change'); 2529 if (stateComponent) jQuery('#nf-field-' + provFieldID).val(stateComponent.short_name).trigger('change'); 2530 } 2531 } else { 2532 if (jQuery('#nf-field-' + countryZipTypeID).val() === 'int') { 2533 const localityComponent = address.address_components.find(ac => ac.types.includes('locality')); 2534 if (localityComponent) jQuery('#nf-field-' + cityFieldID).val(localityComponent.short_name).trigger('change'); 2535 } 2536 } 2537 } else { 2538 console.log('ZIP - Country mismatch'); 2539 Backbone.Radio.channel('fields').request('add:error', model.get('id'), 'zip-code-error', 'Please Enter A Valid ' + (jQuery('#nf-field-' + countryFieldID + " > option[value='" + (jQuery('#nf-field-' + countryFieldID).val()) + "']").text()) + ' postal code.'); 2540 jQuery('#nf-field-' + countryZipStatusID).val("ready").trigger('change'); 2541 } 2542 } 2543 else { 2544 console.log('ZIP - No address with postal code found'); 2545 Backbone.Radio.channel('fields').request('add:error', model.get('id'), 'zip-code-error', 'Please Enter A Valid Zip Code'); 2546 } 2547 } 2548 else { 2549 console.log('ZIP - No results returned'); 2550 Backbone.Radio.channel('fields').request('add:error', model.get('id'), 'zip-code-error', 'Please Enter A Valid Zip Code'); 2551 } 2552 }, 2553 error: (xhr, status, error) => { 2554 console.log('ZIP AJAX ERROR:', {status, error, responseText: xhr.responseText}); 2555 // Only accept on REQUEST_DENIED (API restriction), reject on other errors 2556 Backbone.Radio.channel('fields').request('add:error', model.get('id'), 'zip-code-error', 'Please Enter A Valid Zip Code'); 2557 jQuery('#nf-field-' + countryZipStatusID).val("ready").trigger('change'); 2558 } 2559 }); 2560 } 2561 } 2562 } 2563 }); 2564 new zipCodeTimeZoneValidationController(); 2565} 2566 2567function setEmailValidation() { 2568 const emailValidator = Marionette.Object.extend({ 2569 initialize: function () { 2570 const submitChannel = Backbone.Radio.channel('submit'); 2571 this.listenTo(submitChannel, 'validate:field', this.validate); 2572 const fieldsChannel = Backbone.Radio.channel('fields'); 2573 this.listenTo(fieldsChannel, 'change:modelValue', this.validate); 2574 }, 2575 validate: function (model) { 2576 if (model.get('type') !== 'email') return; 2577 const fieldValue = model.get('value'); 2578 const modelId = model.get('id'); 2579 const errorId = 'custom-email-error'; 2580 const errorMessage = 'Please enter a valid email address!'; 2581 const fieldsChannel = Backbone.Radio.channel('fields'); 2582 if (/^[A-z|0-9|.]+@[A-z|0-9|.]+.[A-z]+$/.test(fieldValue)) { 2583 fieldsChannel.request('remove:error', modelId, errorId); 2584 } else { 2585 fieldsChannel.request('add:error', modelId, errorId, errorMessage); 2586 } 2587 } 2588 }); 2589 new emailValidator(); 2590} 2591 2592function limitDates(dateLimit) { 2593 const dateLimiter = Marionette.Object.extend({ 2594 initialize: function () { 2595 const pikadayChannel = Backbone.Radio.channel('pikaday'); 2596 this.listenTo(pikadayChannel, 'init', this.modifyDatepicker); 2597 },
2598 modifyDatepicker: (dateObject, fieldModel) => { 2599 let currentDate = new Date(); 2600 const tomorrow = currentDate.setDate(currentDate.getDate() + 1); 2601 currentDate = new Date(); 2602 const futureweek = currentDate.setDate(currentDate.getDate() + dateLimit); 2603 dateObject.pikaday._o.disableWeekends = true; 2604 dateObject.pikaday._o.firstDay = 0; 2605 dateObject.pikaday._o.maxDate = futureweek; 2606 dateObject.pikaday._o.minDate = tomorrow; 2607 dateObject.pikaday._o.disableDayFn = (date) => { 2608 const disableDays = ninjaFormDataJS.holidayBlackOuts; 2609 if (_.indexOf(disableDays, moment(date).format("YYYY-MM-DD")) !== -1) { 2610 return true; 2611 } 2612 } 2613 } 2614 }); 2615 new dateLimiter(); 2616} 2617 2618function getLeadSourceID(srcParamTranslatedID) { 2619 const leadSourceID = ninjaFormDataJS.leadSourceID; 2620 2621 jQuery('#nf-field-' + srcParamTranslatedID) 2622 .val(leadSourceID) 2623 .trigger('change'); 2624 2625 // Safely push to dataLayer only if it exists 2626 if (typeof window.dataLayer !== 'undefined' && Array.isArray(window.dataLayer)) { 2627 window.dataLayer.push({ 2628 'tcsLeadSourceID': leadSourceID 2629 }); 2630 } else { 2631 console.warn('dataLayer not found: skipping GTM push'); 2632 } 2633} 2634 2635function programCampusDropDowns(programID, programLabelID, campusID, campusLabelID) { 2636 // Remove any empty data-solo-campus-id options 2637 jQuery(`#nf-field-${programID} option[data-solo-campus-id=""]`).remove(); 2638 2639 jQuery("#nf-field-" + programID).change(() => { 2640 const programValue = jQuery("#nf-field-" + programID).val(); 2641 2642 // Reset campus dropdown with default option 2643 initializeDropdownWithDefault(campusID, 'Choose Campus'); 2644 2645 if (programValue) { 2646 jQuery("#nf-field-" + campusID).append(ninjaFormDataJS.campusesHTML); 2647 2648 // Try to filter using mapping, fallback to data-class if no mapping 2649 filterCampusesByProgramMapping(programValue, campusID, programID); 2650 2651 jQuery("#nf-field-" + campusID + " optgroup").each((index, option) => { 2652 if (jQuery(option).is(':empty')) { 2653 jQuery(option).remove(); 2654 } 2655 }); 2656 } 2657 2658 const programLabel = jQuery("#nf-field-" + programID + " option:selected").text(); 2659 jQuery("#nf-field-" + programLabelID).val(programLabel).trigger('change'); 2660 }); 2661 2662 // Campus change handler for label updates 2663 jQuery("#nf-field-" + campusID).change(() => { 2664 const campusLabel = jQuery("#nf-field-" + campusID + " option:selected").text(); 2665 jQuery("#nf-field-" + campusLabelID).val(campusLabel).trigger('change'); 2666 }); 2667} 2668 2669// Start for semester based on campus 2670function programSemesterDropDowns(campusID, campusLabelID, semesterID, semesterLabelID, programID, programLabelID, selectedFirst) { 2671 2672 jQuery("#nf-field-" + semesterID + " option").remove(); 2673 jQuery("#nf-field-" + semesterID + " optgroup").remove(); 2674 jQuery("#nf-field-" + semesterID).append('<option value="" label="">--</option>'); 2675 2676 let selectedProgramSemesterHTML = ''; 2677 let selectedCampusSemesterHTML = ''; 2678 let semester_selected_ProgValue = ''; 2679 2680 if (selectedFirst == 'program') { 2681 jQuery("#nf-field-" + programID).change(() => { 2682 const semesterProgValue = jQuery("#nf-field-" + programID).val(); 2683 semester_selected_ProgValue = semesterProgValue; 2684 jQuery("#nf-field-" + semesterID + " option").remove(); 2685 jQuery("#nf-field-" + semesterID + " optgroup").remove(); 2686 jQuery("#nf-field-" + semesterID).append('<option value="" label="">Choose Semester</option>'); 2687 jQuery("#nf-field-" + semesterID).append(ninjaFormDataJS.semestersHTML); 2688 2689 jQuery("#nf-field-" + semesterID + " option").each((index, option) => { 2690 let optionSemestersProgid = jQuery(option).attr('data-program-solo-id'); 2691 2692 if (typeof optionSemestersProgid !== 'undefined' && semesterProgValue !== '') { 2693 optionSemestersProgid = optionSemestersProgid.split(' '); 2694 var Semester_len = optionSemestersProgid.length; 2695 2696 if (optionSemestersProgid.indexOf(semesterProgValue) === 0) { 2697 //option.setAttribute('selected','selected'); 2698 } 2699 if (optionSemestersProgid.indexOf(semesterProgValue) === -1 && option.value !== '') { 2700 jQuery(option).remove(); 2701 } 2702 } 2703 }); 2704 2705 jQuery("#nf-field-" + semesterID + " optgroup").each((index, option) => { 2706 if (jQuery(option).is(':empty')) { 2707 jQuery(option).remove(); 2708 } 2709 }); 2710 2711 selectedProgramSemesterHTML = jQuery("#nf-field-" + semesterID).html(); 2712 2713 }); 2714 2715 jQuery("#nf-field-" + campusID).change(() => { 2716 const semesterValue = jQuery("#nf-field-" + campusID).val(); 2717 jQuery("#nf-field-" + semesterID + " option").remove(); 2718 jQuery("#nf-field-" + semesterID + " optgroup").remove(); 2719 jQuery("#nf-field-" + semesterID).append(selectedProgramSemesterHTML); 2720 2721 jQuery("#nf-field-" + semesterID + " option").each((index, option) => { 2722 let option_value_Semesters = jQuery(option).attr('data-class'); 2723 let optionSemesters = ''; 2724 2725 if (typeof option_value_Semesters !== 'undefined' && semesterValue !== '') { 2726 optionSemesters = option_value_Semesters 2727 .split(" ") // Split the string into an array 2728 .filter(pair => pair.startsWith(semester_selected_ProgValue + "-")) // Filter pairs matching the selected ID 2729 .map(pair => pair.split("-")[1]) // Extract only the values 2730 .join(" "); 2731 } 2732 2733 if (typeof optionSemesters !== 'undefined' && semesterValue !== '') { 2734 optionSemesters = optionSemesters.split(' '); 2735 var Semester_len = optionSemesters.length; 2736 2737 if (optionSemesters.indexOf(semesterValue) === 0) { 2738 //option.setAttribute('selected','selected'); 2739 } 2740 if (optionSemesters.indexOf(semesterValue) === -1 && option.value !== '') { 2741 jQuery(option).remove(); 2742 } 2743 } 2744 }); 2745 2746 jQuery("#nf-field-" + semesterID + " optgroup").each((index, option) => { 2747 if (jQuery(option).is(':empty')) { 2748 jQuery(option).remove(); 2749 } 2750 }); 2751 2752 const campusLabel = jQuery("#nf-field-" + campusID + " option:selected").text(); 2753 jQuery("#nf-field-" + campusLabelID).val(campusLabel).trigger('change'); 2754 2755 singleSemesterSelect(semesterID, campusID); 2756 }); 2757 jQuery("#nf-field-" + semesterID).change(() => { 2758 const semesterLabel = jQuery("#nf-field-" + semesterID + " option:selected").text(); 2759 jQuery("#nf-field-" + semesterLabelID).val(semesterLabel).trigger('change'); 2760 }); 2761 2762 } else if (selectedFirst == 'campus') { 2763 2764 jQuery("#nf-field-" + campusID).change(() => { 2765 const semesterValue = jQuery("#nf-field-" + campusID).val(); 2766 jQuery("#nf-field-" + semesterID + " option").remove(); 2767 jQuery("#nf-field-" + semesterID + " optgroup").remove(); 2768 jQuery("#nf-field-" + semesterID).append('<option value="" label="">Choose Semester</option>'); 2769 jQuery("#nf-field-" + semesterID).append(ninjaFormDataJS.semestersHTML); 2770 2771 jQuery("#nf-field-" + semesterID + " option").each((index, option) => { 2772 let optionSemesters = jQuery(option).attr('data-class'); 2773 2774 if (typeof optionSemesters !== 'undefined' && semesterValue !== '') { 2775 optionSemesters = optionSemesters.split(' '); 2776 var Semester_len = optionSemesters.length; 2777 2778 if (optionSemesters.indexOf(semesterValue) === 0) { 2779 //option.setAttribute('selected','selected'); 2780 } 2781 if (optionSemesters.indexOf(semesterValue) === -1 && option.value !== '') { 2782 jQuery(option).remove(); 2783 } 2784 } 2785 }); 2786 2787 jQuery("#nf-field-" + semesterID + " optgroup").each((index, option) => { 2788 if (jQuery(option).is(':empty')) { 2789 jQuery(option).remove(); 2790 } 2791 }); 2792 2793 selectedCampusSemesterHTML = jQuery("#nf-field-" + semesterID).html(); 2794 2795 const campusLabel = jQuery("#nf-field-" + campusID + " option:selected").text(); 2796 jQuery("#nf-field-" + campusLabelID).val(campusLabel).trigger('change'); 2797 2798 singleSemesterSelect(semesterID, campusID); 2799 }); 2800 2801 jQuery("#nf-field-" + programID).change(() => { 2802 const semesterProgValue = jQuery("#nf-field-" + programID).val(); 2803 jQuery("#nf-field-" + semesterID + " option").remove(); 2804 jQuery("#nf-field-" + semesterID + " optgroup").remove(); 2805 jQuery("#nf-field-" + semesterID).append(selectedCampusSemesterHTML); 2806 2807 jQuery("#nf-field-" + semesterID + " option").each((index, option) => { 2808 let optionSemestersProgid = jQuery(option).attr('data-program-solo-id'); 2809 2810 if (typeof optionSemestersProgid !== 'undefined' && semesterProgValue !== '') { 2811 optionSemestersProgid = optionSemestersProgid.split(' '); 2812 var Semester_len = optionSemestersProgid.length; 2813 2814 if (optionSemestersProgid.indexOf(semesterProgValue) === 0) { 2815 //option.setAttribute('selected','selected'); 2816 } 2817 if (optionSemestersProgid.indexOf(semesterProgValue) === -1 && option.value !== '') { 2818 jQuery(option).remove(); 2819 } 2820 } 2821 }); 2822 2823 jQuery("#nf-field-" + semesterID + " optgroup").each((index, option) => { 2824 if (jQuery(option).is(':empty')) { 2825 jQuery(option).remove(); 2826 } 2827 }); 2828 }); 2829 2830 jQuery("#nf-field-" + semesterID).change(() => { 2831 const semesterLabel = jQuery("#nf-field-" + semesterID + " option:selected").text(); 2832 jQuery("#nf-field-" + semesterLabelID).val(semesterLabel).trigger('change'); 2833 }); 2834 2835 } 2836 2837} 2838 2839function singleSemesterSelect(semesterID, campusID) { 2840 var $select = jQuery("#nf-field-" + semesterID); 2841 var optionsWithValue = $select.find("option").filter(function () { 2842 return jQuery(this).val().trim() !== ""; 2843 }); 2844 2845 var optionCountWithValue = optionsWithValue.length; 2846 2847 if (optionCountWithValue === 1) { 2848 optionsWithValue.prop("selected", true); 2849 jQuery("#nf-field-" + semesterID).trigger('change'); 2850 } 2851} 2852 2853// End for semester 2854 2855function nonChainedDropDowns(programID, programLabelID, campusID, campusLabelID, campusDataField) { 2856 // Initialize both dropdowns with default options 2857 initializeDropdownWithDefault(programID, '--'); 2858 initializeDropdownWithDefault(campusID, '--'); 2859 2860 jQuery("#nf-field-" + campusID).change(function () { 2861 const campusLabel = jQuery("#nf-field-" + campusID + " option:selected").text(); 2862 jQuery("#nf-field-" + campusLabelID).val(campusLabel).trigger('change'); 2863 }); 2864 2865 jQuery("#nf-field-" + programID).change(function () { 2866 const programLabel = jQuery("#nf-field-" + programID + " option:selected").text(); 2867 jQuery("#nf-field-" + programLabelID).val(programLabel).trigger('change'); 2868 2869 if (typeof campusDataField !== 'undefined') { 2870 const dataSoloCampusID = jQuery("#nf-field-" + programID + " option:selected").data('solo-campus-id'); 2871 jQuery("#nf-field-" + campusDataField.id).val(dataSoloCampusID).trigger('change'); 2872 if (typeof dataSoloCampusID !== 'undefined') { 2873 const soloCampuses = Object.keys(ninjaFormDataJS.campusData).map(function (key) { 2874 return ninjaFormDataJS.campusData[key]; 2875 }); 2876 const soloCampus = soloCampuses.find(o => o.campus_value == dataSoloCampusID); 2877 if (typeof soloCampus !== 'undefined') { 2878 jQuery("#nf-field-" + campusLabelID).val(soloCampus.campus_label).trigger('change'); 2879 } 2880 } 2881 } 2882 }); 2883} 2884 2885// Non-chained dropdown behavior for forms without school (useDynamic === '') 2886function nonChainedDropDownsNoSchool(programID, programLabelID, campusID, campusLabelID, semesterID, semesterLabelID, campusDataField, countryFieldId, stateFieldId) { 2887 2888 // Initialize all dropdowns with their full options including defaults 2889 // Initialize program field with all options 2890 const $programSelect = jQuery('#nf-field-' + programID); 2891 $programSelect.find('option').remove(); 2892 $programSelect.find('optgroup').remove(); 2893 $programSelect.append('<option value="" label="">Choose Program</option>'); 2894 $programSelect.append(ninjaFormDataJS.programsHTML); 2895 2896 // Initialize campus field with all options 2897 const $campusSelect = jQuery('#nf-field-' + campusID); 2898 $campusSelect.find('option').remove(); 2899 $campusSelect.find('optgroup').remove(); 2900 $campusSelect.append('<option value="" label="">Choose Campus</option>'); 2901 $campusSelect.append(ninjaFormDataJS.campusesHTML); 2902 2903 // Initialize semester field with all options 2904 const $semesterSelect = jQuery('#nf-field-' + semesterID); 2905 $semesterSelect.find('option').remove(); 2906 $semesterSelect.find('optgroup').remove(); 2907 $semesterSelect.append('<option value="" label="">Choose Semester</option>'); 2908 $semesterSelect.append(ninjaFormDataJS.semestersHTML); 2909
2910 // Set up change handlers for labels only (no filtering) 2911 jQuery("#nf-field-" + programID).change(function () { 2912 const programLabel = jQuery("#nf-field-" + programID + " option:selected").text(); 2913 jQuery("#nf-field-" + programLabelID).val(programLabel).trigger('change'); 2914 2915 // Handle campus data field if it exists 2916 if (typeof campusDataField !== 'undefined' && campusDataField) { 2917 const dataSoloCampusID = jQuery("#nf-field-" + programID + " option:selected").data('solo-campus-id'); 2918 jQuery("#nf-field-" + campusDataField.id).val(dataSoloCampusID).trigger('change'); 2919 if (typeof dataSoloCampusID !== 'undefined') { 2920 const soloCampuses = Object.keys(ninjaFormDataJS.campusData).map(function (key) { 2921 return ninjaFormDataJS.campusData[key]; 2922 }); 2923 const soloCampus = soloCampuses.find(o => o.campus_value == dataSoloCampusID); 2924 if (typeof soloCampus !== 'undefined') { 2925 jQuery("#nf-field-" + campusLabelID).val(soloCampus.campus_label).trigger('change'); 2926 } 2927 } 2928 } 2929 runRuleEngine(); 2930 }); 2931 2932 jQuery("#nf-field-" + campusID).change(function () { 2933 const campusLabel = jQuery("#nf-field-" + campusID + " option:selected").text(); 2934 jQuery("#nf-field-" + campusLabelID).val(campusLabel).trigger('change'); 2935 runRuleEngine(); 2936 }); 2937 2938 jQuery("#nf-field-" + semesterID).change(function () { 2939 const semesterLabel = jQuery("#nf-field-" + semesterID + " option:selected").text(); 2940 jQuery("#nf-field-" + semesterLabelID).val(semesterLabel).trigger('change'); 2941 runRuleEngine(); 2942 }); 2943 2944 jQuery("#nf-field-" + countryFieldId?.id).change(function () { 2945 runRuleEngine(); 2946 }) 2947 jQuery("#nf-field-" + stateFieldId?.id).change(function () { 2948 runRuleEngine(); 2949 }) 2950 2951 function runRuleEngine() { 2952 2953 const values = { 2954 program: jQuery("#nf-field-" + programID).val(), 2955 campus: jQuery("#nf-field-" + campusID).val(), 2956 semester: jQuery("#nf-field-" + semesterID).val(), 2957 country: jQuery("#nf-field-" + countryFieldId?.id)?.val(), 2958 state: jQuery("#nf-field-" + stateFieldId?.id)?.val(), 2959 }; 2960 2961 const fieldIDs = { 2962 program: programID, 2963 campus: campusID, 2964 semester: semesterID, 2965 country: countryFieldId?.id, 2966 state: stateFieldId?.id 2967 }; 2968 2969 // STEP 1: RESET everything first 2970 resetRuleEffects(fieldIDs); 2971 2972 // STEP 2: Apply rules fresh 2973 processRules(ninjaFormDataJS.rulesData, values, fieldIDs); 2974 } 2975 2976 // Since Program, Campus, and Semester are independent (non-chained) here, 2977 // auto-select each one on load if it only has a single available option. 2978 autoSelectSingleOption(programID); 2979 autoSelectSingleOption(campusID); 2980 autoSelectSingleOption(semesterID); 2981 2982 runRuleEngine(); // run once on load 2983} 2984 2985function selectProgramCampusUTM(programID, programLabelID, campusID, campusLabelID, campusTarget, utmCampus, utmProgram, dynamicFormType) { 2986 if (dynamicFormType === 'program' && typeof utmProgram !== 'undefined' && utmProgram.value) { 2987 selectProgramUTM(programID, programLabelID, utmProgram); 2988 } 2989 if (typeof campusTarget !== 'undefined') { 2990 const dataSoloCampusID = jQuery("#nf-field-" + programID + " option:selected").data('solo-campus-id'); 2991 jQuery("#nf-field-" + campusTarget.id).val(dataSoloCampusID).trigger('change'); 2992 if (typeof dataSoloCampusID !== 'undefined') { 2993 const soloCampuses = Object.keys(ninjaFormDataJS.campusData).map(function (key) { 2994 return ninjaFormDataJS.campusData[key]; 2995 }); 2996 const soloCampus = soloCampuses.find(o => o.campus_value == dataSoloCampusID); 2997 if (typeof soloCampus !== 'undefined') { 2998 jQuery("#nf-field-" + campusLabelID).val(soloCampus.campus_label).trigger('change'); 2999 } 3000 } 3001 } 3002 else if (utmCampus.value) { 3003 jQuery('#nf-field-' + campusID).val(utmCampus.value).trigger('change'); 3004 const campusLabel = jQuery("#nf-field-" + campusID + " option:selected").text(); 3005 jQuery("#nf-field-" + campusLabelID).val(campusLabel).trigger('change'); 3006 } 3007 if (dynamicFormType !== 'program' && typeof utmProgram !== 'undefined' && utmProgram.value) { 3008 selectProgramUTM(programID, programLabelID, utmProgram); 3009 } 3010} 3011 3012function selectProgramUTM(programID, programLabelID, utmProgram) { 3013 if (utmProgram.value) { 3014 jQuery('#nf-field-' + programID).val(utmProgram.value).trigger('change'); 3015 const programLabel = jQuery("#nf-field-" + programID + " option:selected").text(); 3016 jQuery("#nf-field-" + programLabelID).val(programLabel).trigger('change'); 3017 } 3018} 3019 3020function bindChangesPartChange(timezonefillID, submitButtonID) { 3021 const bindOnChangesOnPartChange = Marionette.Object.extend({ 3022 initialize: function () { 3023 const fieldsChannel = Backbone.Radio.channel('fields'); 3024 this.listenTo(fieldsChannel, 'change:model', this.fieldModelChange); 3025 }, 3026 fieldModelChange: (fieldModel, formModel, conditionModel) => { 3027 if (fieldModel.changed.visible !== true) return; 3028 if ('time_zone_drop_down' == fieldModel.get('key')) { 3029 forumlateTimeZones(fieldModel.get('id'), fieldModel.get('value'), timezonefillID, submitButtonID); 3030 } 3031 } 3032 }); 3033 new bindOnChangesOnPartChange(); 3034} 3035 3036function forumlateTimeZones(modelID, modelValue, timezonefillID, submitButtonID) { 3037 jQuery("#nf-field-" + modelID).change(function () { 3038 if (modelValue) { 3039 const fieldValue = jQuery("#nf-field-" + modelID).val(); 3040 jQuery("#nf-field-" + modelID).parent().append('<img src="' + ninjaFormDataJS.loadingGifPath + '" alt="tcspp-loader" class="timezone-loader" width="118" height="15" />'); 3041 jQuery("#nf-field-" + modelID).prop('disabled', true); 3042 jQuery('#nf-field-' + submitButtonID).prop('disabled', true); 3043 jQuery.ajax({
3044 url: "https://maps.googleapis.com/maps/api/geocode/json?address=" + fieldValue + '&language=en' + "&key=" + ninjaFormDataJS.googleAPIKey, 3045 cache: false, 3046 dataType: "json", 3047 type: "GET", 3048 complete: function () { 3049 jQuery(".timezone-loader").remove(); 3050 jQuery("#nf-field-" + modelID).prop('disabled', false); 3051 jQuery('#nf-field-' + submitButtonID).prop('disabled', false); 3052 }, 3053 success: function (result, success) { 3054 if (success == 'success') { 3055 const lat = result.results[0].geometry.location.lat; 3056 const lng = result.results[0].geometry.location.lng; 3057 const timestamp = Math.floor((new Date).getTime() / 1000); 3058 jQuery.ajax({ 3059 url: "https://maps.googleapis.com/maps/api/timezone/json?location=" + lat + "," + lng + "×tamp=" + timestamp + '&language=en' + "&key=" + ninjaFormDataJS.googleAPIKey, 3060 cache: false, 3061 dataType: "json", 3062 type: "GET", 3063 success: function (result, success) { 3064 const dst = Number(result.dstOffset); 3065 const raw = Number(result.rawOffset); 3066 const adjustedTime = dst + raw; 3067 const isNegative = Math.sign(adjustedTime); 3068 const baseNums = Math.abs(adjustedTime); 3069 if (baseNums > 0) { 3070 const timeinHours = secondsTimeSpanToHMS(baseNums); 3071 let appendDig = ""; 3072 if (isNegative < 0) appendDig = '-'; 3073 else appendDig = '+'; 3074 const timeString = appendDig + timeinHours; 3075 jQuery("#nf-field-" + timezonefillID).val(timeString).trigger('change'); 3076 } 3077 }, 3078 error: function (result, success) { } 3079 }); 3080 } 3081 } 3082 }); 3083 } 3084 }); 3085} 3086 3087function secondsTimeSpanToHMS(seconds) { 3088 const hours = Math.floor(seconds / 3600); 3089 seconds -= hours * 3600; 3090 const minutes = Math.floor(seconds / 60); 3091 seconds -= minutes * 60; 3092 return (hours < 10 ? '0' + hours : hours) + ":" + (minutes < 10 ? '0' + minutes : minutes); //zero padding on minutes and seconds 3093} 3094 3095function setupAddressAutocomplete(countryFieldId) { 3096 jQuery(`#nf-field-${countryFieldId}`).prop('autocomplete', 'country'); 3097 if (typeof jQuery(`#nf-field-${countryFieldId}`)[0] !== 'undefined') { 3098 clearInterval(autocompleteSetupInterval); 3099 } 3100} 3101// NOTE: useDynamicCampusProgram(form) now lives in one place, alongside the 3102// other form-field selector helpers above (see getFormDataSyncSetting).
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.