1/* Two blocks further down this file - the Handlebars helper registrations and the 2 $(document).ready wiring - depend on head assets that can be absent or truncated by the 3 time this file runs. Catching their failure is not enough on its own: the helpers and 4 handlers are then gone for the life of the page even after head-asset recovery puts the 5 missing library back, because nothing re-runs them. Both are defined here as named, 6 idempotent steps with their own completion flags so the recovery block in cmsFunctions.cfc 7 can call MCPlatformUtils.init() after a retry and repair whatever the broken pass dropped. 8 9 Guarded with || so a re-request of this file (which recovery does when platformUtils itself 10 arrived truncated) re-executes the top-level definitions without discarding the flags, and 11 so cannot double-register a document-ready handler. */ 12window.MCPlatformUtils = window.MCPlatformUtils || { handlebarsReady: false, documentReadyWired: false, ready: false }; 13 14MCPlatformUtils.report = function(e) { 15 if (typeof MCJSErrorReporting != 'undefined' && MCJSErrorReporting.exceptionHandler) 16 MCJSErrorReporting.exceptionHandler(e); 17 else 18 console.error('MCJSErrorReporting.exceptionHandler not defined, falling back to console message', e); 19}; 20 21try { 22 var MCKeepAlive = { 23 keepalivetimer: 0, 24 keepalivetimeout: 0, 25 keepaliveinterval: 0, 26 interval: 0, 27 doKeepAlive: function() { 28 this.keepalivetimer = this.keepalivetimer + this.keepaliveinterval; 29 try { TS_AJX('AJAXUTILS','ping',null); } catch (error) {} 30 if (this.keepalivetimer >= this.keepalivetimeout) clearInterval(this.interval); 31 }, 32 startKeepAlive: function(kaIntervalSeconds,kaTimeoutMinutes) { 33 kaIntervalSeconds = typeof(kaIntervalSeconds) != 'undefined' ? kaIntervalSeconds : 300; //Every 5 Minutes 34 kaTimeoutMinutes = typeof(kaTimeoutMinutes) != 'undefined' ? kaTimeoutMinutes : 120; // Two Hours 35 this.keepaliveinterval = kaIntervalSeconds * 1000; 36 this.keepalivetimeout = kaTimeoutMinutes * 60 * 1000; 37 this.interval = setInterval(this.doKeepAlive.bind(this) ,this.keepaliveinterval); 38 } 39 }; 40} catch(e) { 41 if (MCJSErrorReporting && MCJSErrorReporting.exceptionHandler) 42 MCJSErrorReporting.exceptionHandler(e) 43 else 44 console.error(`MCJSErrorReporting.exceptionHandler not defined, falling back to console message`,e); 45} 46 47try { 48 var MCLoader = { 49 urlsAlreadyLoaded: {}, 50 _genericLoader: function(htmlTag,resourceUrl) { 51 // generate a slug from the url to use as a key to track whether or not this has already been loaded 52 let urlSlug = resourceUrl.replace(/[^\w]/gi,'-'); 53 54 // if we don't already have a promisfied loaded for this url, then create one 55 if (!(urlSlug in this.urlsAlreadyLoaded)){ 56 this.urlsAlreadyLoaded[urlSlug] = new Promise(function (resolve, reject) { 57 let element = document.createElement(htmlTag); 58 element.src = resourceUrl; 59 element.onload = function() { 60 resolve(resourceUrl); 61 }; 62 element.onerror = function(event) { 63 let error = new Error(`Failed to load ${resourceUrl}`); 64 if (MCJSErrorReporting && MCJSErrorReporting.exceptionHandler) 65 MCJSErrorReporting.exceptionHandler(error) 66 else 67 console.error('MCJSErrorReporting.exceptionHandler not defined, falling back to console message',error); 68 }; 69 70 // Need to set different attributes depending on htmlTag type 71 switch(htmlTag) { 72 case 'script': 73 element.async = true; 74 element.src = resourceUrl; 75 break; 76 case 'link': 77 element.type = 'text/css'; 78 element.rel = 'stylesheet'; 79 element.href = resourceUrl; 80 } 81 document.head.append(element); 82 }); 83 } 84 return this.urlsAlreadyLoaded[urlSlug]; 85 }, 86 loadJS: function(resourceUrl) { return this._genericLoader('script',resourceUrl)}, 87 loadCSS: function(resourceUrl) { return this._genericLoader('link',resourceUrl)} 88 }; 89} catch(e) { 90 if (MCJSErrorReporting && MCJSErrorReporting.promiseRejectionHandler) 91 MCJSErrorReporting.exceptionHandler(e) 92 else 93 console.error(`MCJSErrorReporting.exceptionHandler not defined, falling back to console message`,e); 94} 95 96// other code in platform might refer to this, so keeping the function name as an alias 97function promiseScriptLoader (scriptURL) { 98 return MCLoader.loadJS(scriptURL); 99} 100 101function textAreaValidation(fieldId, message) { 102 var fieldTxt = $.trim($('#' + fieldId).val()); 103 var returnVal = true; 104 105 if (fieldTxt.length == 0) { 106 alert(message); 107 returnVal = false;
108 } 109 110 return returnVal; 111} 112 113function selectValidation(fieldId, message) { 114 var fieldTxt = $.trim($('#' + fieldId).val()); 115 var returnVal = true; 116 117 if (fieldTxt.length == 0 || fieldTxt == 0) { 118 alert(message); 119 returnVal = false; 120 } 121 122 return returnVal; 123} 124 125// used to include events online meeting code when necessary 126function useEventOnlineMeeting(e,m) { 127 __ev_EOMvar = { eventid:e, mid:m, interval:60, token:'', timer:0 }; 128 129 var scriptsToLoad = []; 130 scriptsToLoad.push(MCLoader.loadJS('/assets/common/javascript/eventsOnlineMeeting.js')); 131 scriptsToLoad.push(MCLoader.loadJS('/assets/common/javascript/jQueryAddons/xdomainrequest/jquery.xdomainrequest.min.js')); 132 133 Promise.all(scriptsToLoad) 134 .then(function(){ 135 __ev_initEOM(__ev_EOMvar.eventid,__ev_EOMvar.mid); 136 }); 137} 138 139//used to facilitate collaspable areas 140function initCollapsibleDivSet (commonClassNameForAllButtonsInCollapsableSet) { 141 var lockTabFound = false; 142 var tabList = $("ul").filter("." + commonClassNameForAllButtonsInCollapsableSet + ":first"); 143 var tabListDataSet = $(tabList).data(); 144 145 if (tabListDataSet != null) { 146 if (!"mccollapsibledivautobuildtabs" in tabListDataSet) 147 tabListDataSet.mccollapsibledivautobuildtabs = 0; 148 if (!"mccollapsibledivviewclass" in tabListDataSet) 149 tabListDataSet.mccollapsibledivviewclass = ""; 150 151 if (tabListDataSet.mccollapsibledivautobuildtabs && tabListDataSet.mccollapsibledivviewclass.length) { 152 $("div").filter("." + tabListDataSet.mccollapsibledivviewclass).each(function(index, thisView){ 153 var thisViewDataSet = $(thisView).data(); 154 if (!"mccollapsibledivviewisselected" in thisViewDataSet) 155 thisViewDataSet.mccollapsibledivviewisselected = 0; 156 if (!"mccollapsibledivviewislocked" in thisViewDataSet) 157 thisViewDataSet.mccollapsibledivviewislocked = 0; 158 if (!"mccollapsibledivviewlabel" in thisViewDataSet) 159 thisViewDataSet.mccollapsibledivviewlabel = $(thisView).id; 160 $(tabList).append(buildCollapsibleDivPillHTML( 161 $(thisView)[0].id, 162 thisViewDataSet.mccollapsibledivviewisselected, 163 thisViewDataSet.mccollapsibledivviewislocked, 164 commonClassNameForAllButtonsInCollapsableSet, 165 tabListDataSet.mccollapsibledivviewclass, 166 thisViewDataSet.mccollapsibledivviewlabel)); 167 }); 168 } 169 } 170 var selectedElement = $("li ." + commonClassNameForAllButtonsInCollapsableSet + ":first"); 171 172 $("li ." + commonClassNameForAllButtonsInCollapsableSet).each(function(index, value){ 173 var thisDataSet = $(value).data(); 174 if (thisDataSet.mccollapsibledivdisabled) { 175 $(value).addClass('tsAppNavButtonDisabled'); 176 } 177 if (thisDataSet.mccollapsibledivlocked) { 178 lockTabFound = true; 179 selectedElement = value; 180 } 181 if (thisDataSet.mccollapsibledivselected) { 182 selectedElement = value; 183 } 184 }); 185 if (lockTabFound) { 186 $("li ." + commonClassNameForAllButtonsInCollapsableSet).addClass('tsAppNavButtonDisabled'); 187 } 188 $(selectedElement).removeClass('tsAppNavButtonDisabled'); 189 190 switchCollapsibleDiv(selectedElement); 191} 192 193function switchCollapsibleDiv (linkElement) { 194 var commonClassNameForAllAreasInCollapsableSet = "." + $(linkElement).data("mccollapsibledivgroup"); 195 var areaToShow = "#" + $(linkElement).data("mccollapsibledivshow"); 196 var allLinkElements = "li ." + $(linkElement).data("mccollapsibledivbuttonclass"); 197 var changeHandler = $(linkElement).parent().parent().data("mccollapsibledivchangehandler"); 198 if (!$(linkElement).hasClass('tsAppNavButtonDisabled')) { 199 $(commonClassNameForAllAreasInCollapsableSet).hide(); 200 $(commonClassNameForAllAreasInCollapsableSet).removeClass('tsAppNavButtonSelected'); 201 $(areaToShow).show(); 202 $(allLinkElements).removeClass('tsAppNavButtonSelected'); 203 $(linkElement).addClass('tsAppNavButtonSelected'); 204 205 206 207 if (changeHandler != null && typeof(window[changeHandler]) === "function") { 208 window[changeHandler]($(areaToShow)[0]); 209 } 210 } 211} 212 213function lockCollapsibleDiv (commonClassNameForAllAreasInCollapsableSet) { 214 $(commonClassNameForAllAreasInCollapsableSet).hide(); 215 $(areaToShow).show(); 216}; 217 218function buildCollapsibleDivPillHTML(viewId, isSelected, isLocked, buttonClass, group, name) { 219 var returnString = ''; 220 221 returnString = '<li class="tsAppNavButton"><a \ 222 id="' + viewId + 'ViewButton" \ 223 data-mcCollapsibleDivShow="' + viewId + '" data-mcCollapsibleDivSelected="' + isSelected + '" \ 224 data-mcCollapsibleDivDisabled="false" data-mcCollapsibleDivLocked="' + isLocked + '" \ 225 data-mcCollapsibleDivButtonClass="' + buttonClass + '" data-mcCollapsibleDivGroup="' + group + '" class="' + buttonClass + '" \ 226 href="##" \ 227 onclick="switchCollapsibleDiv(this);">' + name + '</a></li>'; 228 229 return returnString; 230} 231 232function mcConvertRSSToJSON(rssxml) { 233 var returnItems = []; 234 $(rssxml).find("item").each(function () { // or "item" or whatever suits your feed 235 var el = $(this); 236 returnItems.push({ 237 "title":el.find("title").text(), 238 "link":el.find("link").text(), 239 "description":el.find("description").text(), 240 "pubdate":Date.parse(el.find("pubDate").text()) 241 }); 242 }); 243 return returnItems; 244} 245 246function mcIsElementInViewport(element) { 247 var elementTop = $(element).offset().top; 248 var elementBottom = elementTop + $(element).height(); 249 var viewportTop = $(window).scrollTop(); 250 var viewportBottom = viewportTop + $(window).height(); 251 return elementBottom > viewportTop && elementTop < viewportBottom; 252}; 253 254function mcExecuteMergeTemplates() { 255 // find all merge templates and execute them 256 $('.mcMergeTemplate').each(function(index,thisElement){ 257 return mcExecuteSingleMergeTemplate (thisElement); 258 }); 259} 260 261function mcExecuteSingleMergeTemplate (mergeTemplateElement){ 262 var allowDebug = false;
263 var jsondatavariablename = $(mergeTemplateElement).data('mcjsonvariable'); 264 var jsondata = window[jsondatavariablename]; 265 266 var replaceContainer = false; 267 var mcmergereplacecontainer = $(mergeTemplateElement).data('mcmergereplacecontainer'); 268 var beforeFunctionName = $(mergeTemplateElement).data('mcmergerunbefore'); 269 var afterFunctionName = $(mergeTemplateElement).data('mcmergerunafter'); 270 271 let templateType = "" 272 273 try { 274 templateType = Object.keys(jsondata)[0]; 275 } catch(e) { 276 templateType = "unknown"; 277 } 278 279 gtag('event', 'merge_template_execution', { 280 'type': templateType, 281 'jsondatavariablename': jsondatavariablename, 282 'send_to': 'platformWide' 283 }); 284 285 if ($('#mcEditorBarControlPanelLink').length) { 286 allowDebug = true; 287 } 288 289 if (mcmergereplacecontainer) { 290 switch(mcmergereplacecontainer.toString().toLowerCase().trim()){ 291 case "true": case "yes": case "1": 292 replaceContainer = true; 293 break; 294 default: 295 replaceContainer = false; 296 break; 297 } 298 } 299 300 if (beforeFunctionName) { 301 try { 302 if (beforeFunctionName && window[beforeFunctionName]) { 303 window[beforeFunctionName](); 304 } 305 } catch(e) { 306 if (console) { 307 console.error("Error Running mcmergerunbefore function named '" + beforeFunctionName + "' for mcjsonvariable " + jsondatavariablename); 308 } 309 } 310 } 311 312 try { 313 var hbTemplateSource = $(mergeTemplateElement).html().trim(); 314 if (hbTemplateSource.length) { 315 var hbTemplate = Handlebars.compile(hbTemplateSource); 316 if (replaceContainer) 317 $(mergeTemplateElement).replaceWith((hbTemplate(jsondata))); 318 else 319 $(mergeTemplateElement).html(hbTemplate(jsondata)); 320 } else if (allowDebug) { 321 MCLoader.loadJS('/assets/common/javascript/prettyprint.js') 322 .then(function(){ 323 $(mergeTemplateElement).html(prettyPrint( jsondata )); 324 }); 325 } 326 } catch(e) { 327 if (allowDebug) $(mergeTemplateElement).html("<strong>Error in Handlebars Template</strong><br/>" + e.toString()); 328 } 329 330 if (afterFunctionName) { 331 try { 332 if (afterFunctionName && window[afterFunctionName]) { 333 window[afterFunctionName](); 334 } 335 } catch(e) { 336 if (console) { 337 console.error("Error Running mcmergerunafter function named '" + afterFunctionName + "' for mcjsonvariable " + jsondatavariablename); 338 } 339 } 340 } 341 342 $(mergeTemplateElement).show(); 343} 344 345function mcValidateLogins(scope) { 346 // login -- double click prevention and validation 347 scope.find('.mc_form_login').submit(function () { 348 var ae = []; 349 if ($(this).find('input[name]').filter(function() { return this.name.toLowerCase() == 'username'; }).val().trim() == '') ae[ae.length] = 'Enter your username.'; 350 if ($(this).find('input[name]').filter(function() { return this.name.toLowerCase() == 'password'; }).val().trim() == '') ae[ae.length] = 'Enter your password.'; 351 if (ae.length) { 352 if (typeof mcShowLoginError == 'function') mcShowLoginError(ae); 353 else alert(ae.join('\n')); 354 return false; 355 } else { 356 if (typeof mcHideLoginError == 'function') mcHideLoginError(ae); 357 if (typeof mcShowLoggingIn == 'function') mcShowLoggingIn(ae); 358 else $(this).find('button,input[type="submit"]').replaceWith('<nobr><i class="fa-regular fa-circle-notch fa-lg fa-spin"></i> Signing in...</nobr>'); 359 return true; 360 } 361 }); 362} 363 364/* moment 2.30 tightened strict parsing: the single-letter tokens M, D and h now reject 365 zero-padded values that moment 2.10.6 accepted, so a hand-typed '08/03/2026' or a time 366 typed as '02:30 PM' would silently fail validation. The pickers themselves always emit 367 the unpadded form, but people type the padded one. Hand moment every padding variant -- 368 it tries each in turn and takes the first that validates. 369 370 The variants overlap but never contradict: strict M is /^[1-9]\d?/, which matches '10' 371 just fine, so '10/09/2026' satisfies both MM/DD/YYYY and M/DD/YYYY. That is harmless 372 here because the '/' delimiters fix the segmentation -- each segment yields the same 373 number no matter which variant consumed it. Keep that true of anything added below. 374 375 Strictness itself is preserved, which is the point: partial input ('8/'), garbage, 376 out-of-range values ('13/45/2026') and trailing text are all still rejected. 377 378 If you ever add a strict format using w, W, k, H, m or s, note that 2.30 tightened 379 those too -- w/W/k reject any leading zero, H/m/s reject '08' but allow a bare '0'. 380 Nothing uses them strictly today. 381 382 These lists are only for strict validation; the pickers still format with a single 383 pattern (dateFormat / dateTimeFormat below) and that split is deliberate. */ 384var mca_strictDateFormats = ['M/D/YYYY', 'MM/DD/YYYY', 'M/DD/YYYY', 'MM/D/YYYY']; 385var mca_strictDateTimeFormats = [ 386 'M/D/YYYY - h:mm A', 'M/D/YYYY - hh:mm A', 387 'MM/DD/YYYY - h:mm A', 'MM/DD/YYYY - hh:mm A', 388 'M/DD/YYYY - h:mm A', 'M/DD/YYYY - hh:mm A', 389 'MM/D/YYYY - h:mm A', 'MM/D/YYYY - hh:mm A' 390]; 391 392function mca_setValueForDatePickerField(ct, $input, format) { 393 if (ct) $input.val(moment(ct).format(format)); 394} 395 396function mca_validateOnChangeDateTime(ct, $input, earliest, latest, format) { 397 if (ct) { 398 if (earliest) { 399 var earliestUnixtime = mca_getParsedDateTime(earliest); 400 if (earliestUnixtime && ct.getTime() < earliestUnixtime) { 401 $input.val(moment(earliestUnixtime).format(format)); 402 } 403 } 404 if (latest) { 405 var latestUnixtime = mca_getParsedDateTime(latest); 406 if (latestUnixtime && ct.getTime() > latestUnixtime) { 407 $input.val(moment(latestUnixtime).format(format)); 408 } 409 } 410 } 411} 412 413function mca_setupDatePickerField(f, earliest, latest) { 414 if($('#'+f).length) { 415 var config = { format:'M/D/YYYY', formatDate:'M/D/YYYY', timepicker:false, todayButton:false, scrollInput:false }; 416 config.onChangeMonth = function(ct, $input) { mca_setValueForDatePickerField(ct, $input, config.format); }; 417 config.onChangeYear = function(ct, $input) { mca_setValueForDatePickerField(ct, $input, config.format); }; 418 419 if(earliest && earliest.length >= 8) config.minDate = earliest; 420 if(latest && latest.length >= 8) config.maxDate = latest; 421 422 if ('minDate' in config || 'maxDate' in config) { 423 var ds = 'minDate' in config ? config.minDate : false;
424 var de = 'maxDate' in config ? config.maxDate : false; 425 config.onShow = function(ct, $input) { mca_validateOnChangeDateTime(ct, $input, ds, de, config.format); }; 426 config.onChangeDateTime = function(ct, $input) { mca_validateOnChangeDateTime(ct, $input, ds, de, config.format); }; 427 } 428 429 $.datetimepicker.setDateFormatter('moment'); 430 $('#'+f).datetimepicker('destroy').datetimepicker(config); 431 } 432} 433 434function mca_setupMultipleDatePickerFields(scope, fldclass, earliest, latest) { 435 if(scope && scope.find('.'+fldclass).length) { 436 var config = { format:'M/D/YYYY', formatDate: "M/D/YYYY", timepicker:false, todayButton:false, scrollInput:false }; 437 config.onChangeMonth = function(ct, $input) { mca_setValueForDatePickerField(ct, $input, config.format); }; 438 config.onChangeYear = function(ct, $input) { mca_setValueForDatePickerField(ct, $input, config.format); }; 439 440 if(earliest && earliest.length >= 8) config.minDate = earliest; 441 if(latest && latest.length >= 8) config.maxDate = latest; 442 443 if ('minDate' in config || 'maxDate' in config) { 444 var ds = 'minDate' in config ? config.minDate : false; 445 var de = 'maxDate' in config ? config.maxDate : false; 446 config.onShow = function(ct, $input) { mca_validateOnChangeDateTime(ct, $input, ds, de, config.format); }; 447 config.onChangeDateTime = function(ct, $input) { mca_validateOnChangeDateTime(ct, $input, ds, de, config.format); }; 448 } 449 450 $.datetimepicker.setDateFormatter('moment'); 451 scope.find('.'+fldclass).datetimepicker('destroy').datetimepicker(config); 452 } 453} 454 455function mca_setupDateTimePickerField(f, earliest, latest, step) { 456 if($('#'+f).length) { 457 var config = { format:'M/D/YYYY - h:mm A', formatDate: "M/D/YYYY", formatTime: "h:mm A", timepicker:true, 458 showSecond:false, step:step, todayButton:false, scrollInput:false }; 459 config.onChangeMonth = function(ct, $input) { mca_setValueForDatePickerField(ct, $input, config.format); }; 460 config.onChangeYear = function(ct, $input) { mca_setValueForDatePickerField(ct, $input, config.format); }; 461 462 if(earliest && earliest.length >= 8) config.minDate = earliest; 463 if(latest && latest.length >= 8) config.maxDate = latest; 464 465 if ('minDate' in config || 'maxDate' in config) { 466 var ds = 'minDate' in config ? config.minDate : false; 467 var de = 'maxDate' in config ? config.maxDate : false; 468 config.onShow = function(ct, $input) { mca_validateOnChangeDateTime(ct, $input, ds, de, config.format); }; 469 config.onChangeDateTime = function(ct, $input) { mca_validateOnChangeDateTime(ct, $input, ds, de, config.format); }; 470 } 471 472 $.datetimepicker.setDateFormatter('moment'); 473 $('#'+f).datetimepicker('destroy').datetimepicker(config); 474 } 475} 476 477function mca_setupTimePickerField(f, step) { 478 if($('#'+f).length) { 479 $.datetimepicker.setDateFormatter('moment'); 480 $('#'+f).datetimepicker('destroy').datetimepicker({ datepicker: false, format:"h:mm A", formatTime: "h:mm A", step:step }); 481 } 482} 483 484function mca_setupDatePickerRangeFields(f, t, bothOrNone, earliest, latest) { 485 var minDate = (earliest && earliest.length >= 8) ? earliest : false; 486 var maxDate = (latest && latest.length >= 8) ? latest : false; 487 var dateFormat = 'M/D/YYYY'; 488 var mca_validateEndDateField = function(ct, $input) { 489 if ($('#'+t).val().length > 0) { 490 var endDate = new Date($('#'+t).val()); 491 if (ct && ct.getTime() > endDate.getTime()) $('#'+t).val($('#'+f).val()); 492 } else if(bothOrNone) { 493 $('#'+t).val($('#'+f).val()); 494 } 495 }; 496 497 $.datetimepicker.setDateFormatter('moment'); 498 $('#'+f) 499 .datetimepicker('destroy') 500 .datetimepicker({ 501 format:dateFormat, formatDate:dateFormat, timepicker:false, todayButton:false, scrollInput:false, minDate: minDate, maxDate: maxDate, 502 onChangeMonth:function(ct, $input) { mca_setValueForDatePickerField(ct, $input, dateFormat); }, 503 onChangeYear:function(ct, $input) { mca_setValueForDatePickerField(ct, $input, dateFormat); }, 504 onShow:function(ct, $input) { mca_validateOnChangeDateTime(ct, $input, minDate, maxDate, dateFormat); }, 505 onChangeDateTime:function(ct, $input) { 506 mca_validateOnChangeDateTime(ct, $input, minDate, maxDate, dateFormat); 507 mca_validateEndDateField(ct, $input); 508 } 509 }); 510 511 $('#'+t) 512 .datetimepicker('destroy') 513 .datetimepicker({ 514 format:dateFormat, formatDate:dateFormat, timepicker:false, todayButton:false, scrollInput:false, minDate: minDate, maxDate: maxDate, 515 onShow:function(ct) { this.setOptions({ minDate:$('#'+f).val()?$('#'+f).val():minDate });}, 516 onChangeMonth:function(ct, $input) { mca_setValueForDatePickerField(ct, $input, dateFormat); }, 517 onChangeYear:function(ct, $input) { mca_setValueForDatePickerField(ct, $input, dateFormat); }, 518 onChangeDateTime:function(ct, $input) { 519 mca_validateOnChangeDateTime(ct, $input, $('#'+f).val()?$('#'+f).val():minDate, maxDate, dateFormat); 520 if(bothOrNone && !$('#'+f).val().length) $('#'+f).val($('#'+t).val()); 521 } 522 }); 523 524 $('#'+f).off('keyup').on('keyup',function() { 525 try { 526 if (moment($('#'+f).val(),mca_strictDateFormats,true).isValid()) { 527 let ct = new Date(moment($('#'+f).val(),mca_strictDateFormats,true)); 528 let dtInput = $('#'+f); 529 mca_validateOnChangeDateTime(ct, dtInput, minDate, maxDate, dateFormat); 530 mca_validateEndDateField(ct, dtInput); 531 } 532 } catch(e) {} 533 }); 534} 535 536function mca_setupDateTimePickerRangeFields(f, t, step, minDate) { 537 var dateTimeFormat = 'M/D/YYYY - h:mm A';
538 var mca_clearIfPastDate = function(ct, dtEl){ 539 var todayDate = new Date(); 540 todayDate.setHours(0, 0, 0, 0); 541 if (todayDate.getTime() > ct.getTime()){ 542 dtEl.val(''); 543 return true; 544 } 545 return false; 546 } 547 var mca_onChangeStartDateTime = function(ct, $input) { 548 if (ct && minDate == 0 && mca_clearIfPastDate(ct, $('#'+f))) return; 549 550 if (ct && $('#'+t).val().length > 0) { 551 var endDate = new Date($('#'+t).val().replace(' - ',' ')); 552 if (ct.getTime() > endDate.getTime()) $('#'+t).val($('#'+f).val()); 553 } 554 }; 555 var mca_onChangeEndDateTime = function(ct, $input) { 556 if (ct && minDate == 0 && mca_clearIfPastDate(ct, $('#'+t))) return; 557 558 if (ct && $('#'+f).val().length > 0) { 559 var startDate = new Date($('#'+f).val().replace(' - ',' ')); 560 if (startDate.getTime() > ct.getTime()) $('#'+t).val($('#'+f).val()); 561 } 562 }; 563 564 $.datetimepicker.setDateFormatter('moment'); 565 566 var fromDateConfig = { 567 format:dateTimeFormat, formatDate: "M/D/YYYY", formatTime: "h:mm A", timepicker:true, showSecond:false, step:step, todayButton:false, scrollInput:false, 568 onChangeMonth:function(ct, $input) { mca_setValueForDatePickerField(ct, $input, dateTimeFormat); }, 569 onChangeYear:function(ct, $input) { mca_setValueForDatePickerField(ct, $input, dateTimeFormat); }, 570 onChangeDateTime:mca_onChangeStartDateTime 571 }; 572 573 var toDateConfig = { 574 format:dateTimeFormat, formatDate: "M/D/YYYY", formatTime: "h:mm A", timepicker:true, showSecond:false, step:step, todayButton:false, scrollInput:false, 575 onShow:function(ct) { 576 this.setOptions({ 577 minDate: $('#'+f).val() ? $('#'+f).val() : (typeof(minDate) != 'undefined' ? minDate : false) 578 }); 579 }, 580 onChangeMonth:function(ct, $input) { mca_setValueForDatePickerField(ct, $input, dateTimeFormat); }, 581 onChangeYear:function(ct, $input) { mca_setValueForDatePickerField(ct, $input, dateTimeFormat); }, 582 onChangeDateTime:mca_onChangeEndDateTime 583 }; 584 585 if(typeof(minDate) != 'undefined') { 586 fromDateConfig.minDate = minDate; 587 } 588 589 $('#'+f).datetimepicker('destroy').datetimepicker(fromDateConfig); 590 $('#'+t).datetimepicker('destroy').datetimepicker(toDateConfig); 591 592 $('#'+f).off('keyup').on('keyup',function() { 593 try { 594 if (moment($('#'+f).val(),mca_strictDateTimeFormats,true).isValid()) { 595 mca_onChangeStartDateTime(new Date(moment($('#'+f).val(),mca_strictDateTimeFormats,true)),$('#'+f)); 596 } 597 } catch(e) {} 598 }); 599} 600 601function mca_setOptionsForDatePickerField(f,opts) { 602 $('#'+f).datetimepicker('setOptions',opts); 603} 604 605function mca_getParsedDateTime(dt) { 606 var parsedDateTime; 607 try { parsedDateTime = dt.length ? new Date(dt.replace(' - ',' ')).getTime() : null; } catch (e) { }; 608 return parsedDateTime; 609} 610 611function mca_clearDateRangeField(f,t) { 612 if(f && $('#'+f).length) $('#'+f).val('').trigger('change'); 613 if(t && $('#'+t).length) $('#'+t).val('').trigger('change'); 614} 615 616function mca_setupCalendarIcons(scopeID) { 617 $('#' + scopeID + ' span.calendar-button').click(function() { 618 $('#' + $(this).data('target') + '.dateControl').focus(); 619 }); 620} 621 622function mc_isValidBillingZip(z,sid,sc) { 623 if ((sid == 0 && sc == '') || z.trim().length == 0) return false; 624 625 let isValidZip = false; 626 627 let chkZipResult = (r) => { 628 if (r && r.isvalidzip) isValidZip = true; 629 }; 630 631 let objParams = { billingZip:z, billingStateID:sid, billingState:sc }; 632 TS_AJX_SYNC('TSCOMMON','checkBillingZIP',objParams,chkZipResult,chkZipResult,5000,chkZipResult); 633 634 return isValidZip; 635} 636 637function mca_initConfirmButton(actionBtn, onConfirmHandler, ovActionBtnHTML, ovConfirmBtnHTML, onConfirmLoadingText){ 638 // the disabled class only blocks pointer events, so guard here for keyboard activation while a confirmed action is in flight 639 if (actionBtn.hasClass('disabled')) return; 640 641 if (actionBtn.attr('data-confirm') == 1) { 642 actionBtn.attr('data-confirm',0).addClass('disabled').html(onConfirmLoadingText ? onConfirmLoadingText : 'Deleting...'); 643 onConfirmHandler(); 644 } else { 645 actionBtn.attr('data-confirm',1).html(ovConfirmBtnHTML ? ovConfirmBtnHTML : '<i class="fa-solid fa-circle-info"></i> Confirm'); 646 setTimeout(function(actionBtn){ 647 if (actionBtn.attr('data-confirm') == 1) { 648 actionBtn.attr('data-confirm',0).removeClass('disabled').html(ovActionBtnHTML ? ovActionBtnHTML : '<i class="fa-solid fa-tras
648h-can"></i>'); 649 } 650 },2500,actionBtn); 651 } 652} 653 654function mca_sanitizeInvNumInput(o,el) { 655 let val = el.value.trim(); 656 657 // Strip all chars except letters, digits, and dash 658 val = val.replace(/[^a-zA-Z0-9-]/g, ''); 659 660 if (val.includes("-")) { 661 // if dash, grab everything after dash 662 val = val.substring(val.lastIndexOf("-") + 1); 663 } else { 664 // if no dash, replace the o 665 let regex = new RegExp(o, 'i'); 666 val = val.replace(regex, ''); 667 } 668 669 // Remove non-digit characters and strip leading zeros 670 val = val.replace(/\D/g, "").replace(/^0+/, ''); 671 672 el.value = val; 673} 674 675var helpersAlreadyTriggered = {}; 676 677function triggerHelperEvent(helperName) { 678 try { 679 if (!(helperName in helpersAlreadyTriggered)) { 680 //only trigger event per helper once per pageload 681 helpersAlreadyTriggered[helperName]=1; 682 gtag('event', 'merge_template_helper', { 683 'helper': helperName, 684 'send_to': 'platformWide' 685 }); 686 } 687 } catch(e){} 688} 689 690/* Missing Handlebars is checked up front rather than left to throw on the first 691 registerHelper call, so the report names the actual fault - an empty helper set - instead 692 of a generic "Handlebars is not defined" that says nothing about what the page lost. */ 693MCPlatformUtils.registerHandlebarsHelpers = function() { 694 if (MCPlatformUtils.handlebarsReady) return true; 695 if (typeof Handlebars == 'undefined') { 696 MCPlatformUtils.report(new Error('platformUtils: Handlebars unavailable - none of its Handlebars helpers are registered')); 697 return false; 698 } 699 try { 700Handlebars.registerHelper('moment', function(context, block) { 701 702 triggerHelperEvent('moment'); 703 704 if (context && context.hash) { 705 block = $.extend(true, {}, context); 706 context = undefined; 707 } 708 var date = moment(context); 709 var hasFormat = false; 710 711 for (var i in block.hash) { 712 if (i === 'format') { 713 hasFormat = true; 714 } else if (date[i]) { 715 date = date[i](block.hash[i]); 716 } else { 717 console.log('moment.js does not support "' + i + '"'); 718 } 719 } 720 721 if (hasFormat) { 722 date = date.format(block.hash.format); 723 } 724 return date; 725}); 726Handlebars.registerHelper('inArray', function(elem, list, options) { 727 728 triggerHelperEvent('inArray'); 729 730 if(list.indexOf(elem) > -1) { 731 return list.indexOf(elem); 732 } 733 return -1; 734}); 735 736Handlebars.registerHelper('duration', function(context, block) { 737 triggerHelperEvent('duration'); 738 if (context && context.hash) { 739 block = $.extend(true, {}, context); 740 context = 0; 741 } 742 var duration = moment.duration(context); 743 var hasFormat = false; 744 745 // Reset the language back to default before doing anything else 746 duration = duration.lang('en'); 747 748 for (var i in block.hash) { 749 if (i === 'format') { 750 hasFormat = true; 751 } else if (duration[i]) { 752 duration = duration[i](block.hash[i]); 753 } else { 754 console.log('moment.js duration does not support "' + i + '"'); 755 } 756 } 757 758 if (hasFormat) { 759 duration = duration.format(block.hash.format); 760 } 761 return duration; 762}); 763 764Handlebars.registerHelper('compare', function (lvalue, operator, rvalue, options) { 765 766 var operators, result; 767 768 triggerHelperEvent('compare'); 769 if (arguments.length < 3) { 770 throw new Error("Handlerbars Helper 'compare' needs 2 parameters"); 771 } 772 773 if (options === undefined) { 774 options = rvalue; 775 rvalue = operator; 776 operator = "==="; 777 } 778 779 operators = { 780 '==': function (l, r) { return l == r; }, 781 '===': function (l, r) { return l === r; }, 782 '!=': function (l, r) { return l != r; }, 783 '!==': function (l, r) { return l !== r; }, 784 '<': function (l, r) { return l < r; }, 785 '>': function (l, r) { return l > r; }, 786 '<=': function (l, r) { return l <= r; }, 787 '>=': function (l, r) { return l >= r; }, 788 '&&': function (l, r) { return l && r; }, 789 '||': function (l, r) { return l || r; }, 790 'typeof': function (l, r) { return typeof l == r; } 791 }; 792 793 if (!operators[operator]) { 794 throw new Error("Handlerbars Helper 'compare' doesn't know the operator " + operator); 795 } 796 797 result = operators[operator](lvalue, rvalue); 798 799 if (result) { 800 return options.fn(this); 801 } else { 802 return options.inverse(this); 803 } 804 805}); 806 807Handlebars.registerHelper ('truncate', function (str, len) { 808 triggerHelperEvent('truncate');{} 809 810 if (str.length > len && str.length > 0) { 811 var new_str = str + " "; 812 new_str = str.substr (0, len); 813 new_str = str.substr (0, new_str.lastIndexOf(" ")); 814 new_str = (new_str.length > 0) ? new_str : str.substr (0, len); 815 816 return new Handlebars.SafeString ( new_str +'...' ); 817 } 818 819 return str; 820}); 821 822Handlebars.registerHelper('stripHTMLtags', function(context) { 823 triggerHelperEvent('stripHTMLtags'); 824 825 var returnText = context.replace(/<\/?[a-z][a-z0-9]*[^<>]*>/ig, ""); 826 returnText=returnText.replace(/ /g,''); 827 return returnText; 828}); 829 830Handlebars.registerHelper('arrayToList', function(arrayValue,stringDelimiter) { 831 triggerHelperEvent('arrayToList'); 832 return arrayValue.join(stringDelimiter); 833}); 834 835Handlebars.registerHelper("math", function(lvalue, operator, rvalue, options) { 836 lvalue = parseFloat(lvalue); 837 rvalue = parseFloat(rvalue); 838 839 triggerHelperEvent('math'); 840 return { 841 "+": lvalue + rvalue, 842 "-": lvalue - rvalue, 843 "*": lvalue * rvalue, 844 "/": lvalue / rvalue, 845 "%": lvalue % rvalue 846 }[operator]; 847}); 848 849Handlebars.registerHelper('var',function(name, value, context){ 850 triggerHelperEvent('var'); 851 this[name] = value; 852}); 853 854Handlebars.registerHelper('for', function(from, to, incr, block) { 855 var accum = ''; 856 857 triggerHelperEvent('for'); 858 for(var i = from; i < to; i += incr) 859 accum += block.fn(i); 860 return accum; 861}); 862 863Handlebars.registerHelper ('urlencodedformat', function (str) { 864 865 triggerHelperEvent('urlencodedformat'); 866 return encodeURIComponent(str).replace(/'/g, "%27"); 867}); 868 869/* taken from https://github.com/wycats/handlebars.js/issues/927##issuecomment-318640459 */ 870Handlebars.__switch_stack__ = []; 871Handlebars.registerHelper("switch", function( value, options ) { 872 873 triggerHelperEvent('switch'); 874 875 Handlebars.__switch_stack__.push({ 876 switch_match : false, 877 switch_value : value 878 }); 879 var html = options.fn( this ); 880 Handlebars.__switch_stack__.pop(); 881 return html; 882}); 883Handlebars.registerHelper( "case", function( value, options ) { 884 var args = Array.from( arguments ); 885 var options = args.pop(); 886 var caseValues = args; 887 var stack = Handlebars.__switch_stack__[Handlebars.__switch_stack__.length - 1]; 888 889 triggerHelperEvent('case'); 890
891 if ( stack.switch_match || caseValues.indexOf( stack.switch_value ) === -1 ) { 892 return ''; 893 } else { 894 stack.switch_match = true; 895 return options.fn( this ); 896 } 897}); 898Handlebars.registerHelper( "default", function( options ) { 899 var stack = Handlebars.__switch_stack__[Handlebars.__switch_stack__.length - 1]; 900 901 triggerHelperEvent('default'); 902 903 if ( !stack.switch_match ) { 904 return options.fn( this ); 905 } 906}); 907Handlebars.registerHelper('incremented', function (index) { 908 index++; 909 910 triggerHelperEvent('incremented'); 911 912 return index; 913}); 914 MCPlatformUtils.handlebarsReady = true; 915 } catch(e) { 916 /* A throw part-way through leaves the helper set incomplete, so the flag stays false 917 and init() re-runs the whole set once recovery has restored what was missing. 918 registerHelper overwrites by name, so replaying a partly-applied set is safe. */ 919 MCPlatformUtils.report(e); 920 } 921 return MCPlatformUtils.handlebarsReady; 922}; 923 924function mc_setCookieConsent(optOut, notyUID) { 925 if (window.mc_notyInstances && window.mc_notyInstances[notyUID]) { 926 $(window.mc_notyInstances[notyUID].barDom).find('.noty_buttons button').prop('disabled', true); 927 } 928 TS_AJX('PLATFORM', 'setCookieConsent', {optOut: optOut}, function(r){ 929 if (window.mc_notyInstances && window.mc_notyInstances[notyUID]) { 930 window.mc_notyInstances[notyUID].close(); 931 delete window.mc_notyInstances[notyUID]; 932 } 933 if (window.dataLayer) { 934 gtag('consent', 'update', { 935 'ad_storage': optOut ? 'denied' : 'granted', 936 'analytics_storage': optOut ? 'denied' : 'granted' 937 }); 938 dataLayer.push({event: 'mc_cookie_consent', optOut: optOut}); 939 } 940 }); 941} 942 943function mc_getCookieConsentStatus() { 944 try { return window._mc_nec_optOut === true; } catch(e) { return false; } 945} 946 947MCPlatformUtils.wireDocumentReady = function() { 948 if (MCPlatformUtils.documentReadyWired) return true; 949 if (typeof jQuery == 'undefined') { 950 MCPlatformUtils.report(new Error('platformUtils: jQuery unavailable - merge templates and login validation are not wired')); 951 return false; 952 } 953 try { 954$(document).ready(function () { 955 mcExecuteMergeTemplates(); 956 mcValidateLogins($('body')); 957 958 // standard reusable double click prevention 959 $('.mc_btn_submitonce').removeAttr('disabled'); 960 $('.mc_form_submitonce').submit(function () { 961 $('.mc_btn_submitonce').attr("disabled", true); 962 return true; 963 }); 964}); 965 MCPlatformUtils.documentReadyWired = true; 966 } catch(e) { 967 MCPlatformUtils.report(e); 968 } 969 return MCPlatformUtils.documentReadyWired; 970}; 971 972/* Both steps run from here, and run again whenever the head-asset recovery block calls this 973 after a retry. Each step no-ops if it already succeeded, so a second call restores only 974 what the broken pass dropped. ready is the single answer to "is this file's init actually 975 complete", which is what recovery reports on rather than assuming a restored file is 976 enough. */ 977MCPlatformUtils.init = function() { 978 var handlebarsReady = MCPlatformUtils.registerHandlebarsHelpers(); 979 var documentReadyWired = MCPlatformUtils.wireDocumentReady(); 980 MCPlatformUtils.ready = handlebarsReady && documentReadyWired; 981 return MCPlatformUtils.ready; 982}; 983 984MCPlatformUtils.init(); 985 986 987iOS_hoverFixLastItemClickedMarker = null; 988 989function iOS_hoverFix_isTouchDevice(){ 990 return "ontouchstart" in window || window.DocumentTouch && document instanceof DocumentTouch; 991} 992 993function iOS_hoverFix(selector) { 994 if (iOS_hoverFix_isTouchDevice()) { 995 $(selector).each(function() { 996 $(this).bind('click', function() { 997 if ((iOS_hoverFixLastItemClickedMarker != this)) { 998 iOS_hoverFixLastItemClickedMarker = this; 999 return false; 1000 } 1001 else { 1002 iOS_hoverFixLastItemClickedMarker = null; 1003 return true; 1004 } 1005 }); 1006 }); 1007 } 1008} 1009 1010async function dynamicallyLoadUppy() { 1011 try { 1012 let { Uppy, Form, Dashboard, XHRUpload } = await import('//cdn.jsdelivr.net/npm/[email protected]/dist/uppy.min.mjs'); 1013 await MCLoader.loadCSS("//cdn.jsdelivr.net/npm/[email protected]/dist/uppy.min.css"); 1014 window.Uppy = Uppy; 1015 window.Form = Form; 1016 window.XHRUpload = XHRUpload; 1017 window.Dashboard = Dashboard; 1018 } catch(e) { 1019 console.error('Failed to load Uppy',e); 1020 } 1021} 1022/* 1023 Replaces the plupload queue widget's plain "file too large" alert with one that also 1024 states the configured limit. This lived as a local patch inside 1025 jquery.plupload.queue.js (MCDEV-8305) until MCDEV-12304, and was moved out so the 1026 vendored plupload bundle stays byte-identical to upstream and stays trivial to upgrade. 1027 1028 Binds at a higher priority than the widget's own Error handler so this runs first, then 1029 returns false to stop propagation - that suppresses the widget's alert rather than 1030 producing a second one. Only FILE_SIZE_ERROR is short-circuited; every other error code 1031 falls through to the widget untouched. Files rejected on size never enter the queue, so
1032 the widget's row-styling branch is a no-op for this code and nothing is lost by 1033 skipping it. 1034 1035 Stopping the chain also skips the page's own init.Error handler, though, because plupload 1036 binds those inside uploader.init() - i.e. AFTER the widget bound its handler - so they sit 1037 further down the same chain. Losing them would be a real regression: the blog and page 1038 JSON import screens record rejections in their upload summary from init.Error. So the 1039 page's handler is invoked directly below before returning false. 1040 1041 Call with the queue instance right after initializing it: 1042 mcPluploadSizeLimitAlert($("#uploader").pluploadQueue()); 1043*/ 1044function mcPluploadSizeLimitAlert(uploader) { 1045 if (!uploader || typeof plupload == 'undefined') return uploader; 1046 1047 uploader.bind('Error', function(up, err) { 1048 if (err.code != plupload.FILE_SIZE_ERROR || !err.file) return; 1049 1050 var prefix = plupload.translate('Error: File too large:') || 'Error: File too large:'; 1051 alert(prefix + " " + err.file.name + '. Upload a file smaller than '+ up.settings.filters.max_file_size +'.'); 1052 1053 // Hand off to the page's own Error handler, which returning false would otherwise skip. 1054 // plupload reads the init map the same way (see initControls in plupload.dev.js), and it 1055 // binds those handlers with the uploader as scope, so mirror that here. 1056 var pageInit = up.getOption('init'); 1057 if (pageInit && typeof pageInit.Error == 'function') { 1058 try { 1059 pageInit.Error.call(up, up, err); 1060 } catch (e) { 1061 if (typeof console != 'undefined' && console.error) console.error('plupload page Error handler threw', e); 1062 } 1063 } 1064 1065 return false; 1066 }, null, 1); 1067 1068 return uploader; 1069}
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.