PageSourceSearch

https://www.itla.org/assets/common/javascript/platformUtils.js?mcts=20260928183520

js itla.org collected 2026-09-29 11:36:23 UTC 38,868 bytes, 1,069 lines download raw bytes

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(/&nbsp;/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.