PageSourceSearch

https://chattanoogatrackclub.org/tpWheels/javascripts/tpWheels.js?salt=20260922064108

js chattanoogatrackclub.org collected 2026-10-08 17:34:07 UTC 137,687 bytes, 3,646 lines download raw bytes

1/* Enumerator for common keys that we may want to perform actions against.
2** NOTE: The keycodes here are not valid for the keypress function. Use keydown or keyup instead. */
3
4	/* Use this function to detect & add new keys to the enumerator
5		$(document.body).keydown(function(event) {
6			var keyCode = event.which ? event.which : event.keyCode;
7			// console.log(event.originalEvent);
8			$(document.body).append("<p>" + String.fromCharCode(keyCode) + ":" + keyCode + "</p>");
9		});
10	*/
11
12	var KEYENUM = {
13		// Alphabet
14		A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71, H: 72, I: 73, J: 74, K: 75, L: 76, M: 77,
15		N: 78, O: 79, P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87, X: 88, Y: 89, Z: 90,
16
17		// Numbers
18		NUMROW0: 48, NUMROW1: 49, NUMROW2: 50, NUMROW3: 51, NUMROW4: 52, NUMROW5: 53, NUMROW6: 54, NUMROW7: 55, NUMROW8: 56, NUMROW9: 57,
19		NUMPAD0: 96, NUMPAD1: 97, NUMPAD2: 98, NUMPAD3: 99, NUMPAD4: 100, NUMPAD5: 101, NUMPAD6: 102, NUMPAD7: 103, NUMPAD8: 104, NUMPAD9: 105,
20
21		// Arrows
22		LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40,
23
24		// Special Characters
25		BSLASH: 220, COMMA: 188, EQUALSIGN: 187, FSLASH: 191, GRAVE: 192, HYPHEN: 189, LBRACKET: 219, MINUS: 109, PERIOD: 190, PLUS: 107, RBRACKET: 221, SEMICOLON: 186, TICK: 222, FORWARDSLASH: 191, NUMPADFORWARDSLASH: 111,
26
27		// Modifiers
28		ALT: 18, CONTROL: 17, SHIFT: 16,
29
30		// Operators
31		BACKSPACE: 8, DEL: 46, END: 35, ESC: 27, HOME: 36, INSERT: 45, PAGEDOWN: 34, PAGEUP: 33, RETURN: 13, TAB: 9
32	};
33
34/* Cookie funcions from: http://www.w3schools.com/js/js_cookies.asp
35	Set these up before the jQuery binding so we can ensure the visbility logic below has them available to run. */
36	function setCookie(cname, cvalue, exdays, path) {
37		var d = new Date();
38		d.setTime(d.getTime() + (exdays*24*60*60*1000));
39		document.cookie = cname + "=" + cvalue + "; expires=" + d.toUTCString() + "; path=" + (path || "/");
40	}
41
42	function getCookie(cname) {
43		var name = cname + "=";
44		var ca = document.cookie.split(';');
45		for(var i=0; i<ca.length; i++) {
46			var c = ca[i];
47			while (c.charAt(0)==' ') c = c.substring(1);
48			if (c.indexOf(name) != -1) return c.substring(name.length,c.length);
49		}
50		return "";
51	}
52
53	function deleteCookie(cname, path) {
54		document.cookie = cname + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=" + (path || "/");
55	}
56
57/* Generate GUID
58	Script from: http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript */
59	generateGUID = function() {
60		return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {var r = Math.random()*16|0,v=c=='x'?r:r&0x3|0x8;return v.toString(16);});
61	};
62
63// Name the browser tab if it does not have one
64	if (window.name == '')
65		window.name = generateGUID();
66
67	/* Set up the cookie for maintaining the active tab
68		TEMPORARY: Clean up any non root activeTab cookies */
69		var activeTabMatches = document.cookie.match(/activeTab/g);
70		if (activeTabMatches != null && typeof activeTabMatches != "undefined" && activeTabMatches.length > 1) {
71			var path = window.location.pathname;
72			while (path != "") {
73				document.cookie = "activeTab=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=" + path;
74				path = path.replace(/(\/[^\/]*?)$/, '');
75			}
76		}
77
78		// Now set the activeTab if we do not have one
79		if (getCookie("activeTab") == "")
80			setCookie("activeTab", window.name, 365);
81
82/* Setup tab focusing / leaving
83	NOTE! Always make sure this script runs first so following logic can use the visibility values as needed */
84	var visibilityKey_hidden, visibilityKey_changeEvent;
85
86	// Detect which version of the API is available
87	if (typeof document.hidden !== "undefined") {
88		visibilityKey_hidden = "hidden";
89		visibilityKey_changeEvent = "visibilitychange";
90
91	} else if (typeof document.mozHidden !== "undefined") {
92		visibilityKey_hidden = "mozHidden";
93		visibilityKey_changeEvent = "mozvisibilitychange";
94
95	} else if (typeof document.msHidden !== "undefined") {
96		visibilityKey_hidden = "msHidden";
97		visibilityKey_changeEvent = "msvisibilitychange";
98
99	} else if (typeof document.webkitHidden !== "undefined") {
100		visibilityKey_hidden = "webkitHidden";
101		visibilityKey_changeEvent = "webkitvisibilitychange";
102	}
103
104	// If the browser supports the visibility API then set everything up
105	if (visibilityKey_hidden !== "undefined") {
106		$(document).bind(visibilityKey_changeEvent, function(event) {
107			if (document[visibilityKey_hidden]) {
108			} else {
109			}
110		});
111	}
112
113/* Unfocus / focus functions for the window.
114	These are different than the visibility functions above. The visibility ones will still function even if the user is using a different application but that tab is still visible.
115	The onblur below will still fire even if the tab is still open in the browser but the user is using a different application. */
116	window.onblur = function() {
117	};
118
119	window.onfocus = function() {
120		setCookie("activeTab", window.name, 365);
121	};
122
123/* jQuery document ready
124** Bind all custom tpWheels logic inside here*/
125$(document).ready(function() {
126	/* jQuery plugins settings
127	** Set defaults for jQuery plugins here. */
128
129	// 	$.datepicker.setDefaults({
130	// 		changeMonth: true,
131	// 		changeYear: true,
132	// 		showOn: "button",
133	// 		buttonImage: "/tpWheels/images/icons/calendar.png",
134	// 		buttonImageOnly: true,
135	// 		buttonText: "",
136	// 		onSelect: function(dateText, inst) {
137	// 			$(inst.input).change();
138	// 		}
139	// 	});
140
141	// Setup the context menu on click
142	$("div.context-menu").livequery(function() {
143		var contextMenu = this,
144			openLinksInNewTab = $(contextMenu).data("open-links-in-new-tab") ? new Boolean($(contextMenu).data("open-links-in-new-tab")) : false;
145
146		// Create a span element and and bind the click event to show / hide the context menu and append it to the contextMenu
147		$('<span class="context-menu">&#9660;</span>')
148			.click(function() {
149				// Set the positon
150				if (!$(contextMenu).is(":visible")) {
151					var inTopNav = $(contextMenu).closest("ul#main-nav").length > 0;
152					if (!inTopNav)
153						$(contextMenu).css("left", $(this).position().left);
154					else
155						$(contextMenu).parent().css("position", "relative");
156					$(contextMenu).css("top", $(this).position().top + $(this).height());
157				}
158				// Show/Hide the context menu
159				$(contextMenu).toggle();
160
161				// If context menu is visible, add few classes, load data if nexessary
162				if ($(contextMenu).is(":visible")) {
163					// Add necessary classes
164					$(this).html('&#9650;').addClass("on").removeClass("off");
165
166					// Calculate the width for top-border, subtracting 2 to compensate for 2px margin
167					widthForTopBorder = $(this).outerWidth() - 2;
168
169					// If this is a dynamic context menu, request the server for data
170					if ($(contextMenu).attr("rel") != undefined) {
171						var contextMenuOptions = $('<div class="context-menu-options" />').appendTo(contextMenu);
172						appendLoadingMask(contextMenuOptions);
173						$.get($(contextMenu).attr("rel"), {}, function(data) {
174							$(contextMenuOptions).html(data);
175
176							// Set all links as target="_blank" if requested and the link is not remote or a facebox link
177							if (openLinksInNewTab) {
178								$(contextMenu).find("a").filter(":not(.facebox)").each(function() {
179									if (!$(this).data("remote"))
180										$(this).attr("target", "_blank");
181								});
182							}
183
184							// Append div to render context menu's top-border
185							$('<div class="context-menu-top" />').css("left", widthForTopBorder).css("width", $(contextMenu).outerWidth() - widthForTopBorder - 1).prependTo(contextMenu);
186						});
187
188					// Append div to render context menu's top-border
189					} else {
190						$('<div class="context-menu-top" />').css("left", widthForTopBorder).css("width", $(contextMenu).outerWidth() - widthForTopBorder - 1).prependTo(contextMenu);
191					}
192
193				// Remove elements from context-menu
194				} else {
195					$(contextMenu).find("div.context-menu-top").remove().end().find("div.context-menu-options").remove();
196					$(this).html('&#9660;').addClass("off").removeClass("on");
197				}
198			})
199			.insertAfter(contextMenu);
200	});
201
202	// handle file fields in ajax requests
203	$("form").on("ajax:aborted:file", function(e, fields) {
204		var form = $(this);
205		$(form).one("ajax:beforeSend.multipart", function(e, xhr, settings) {
206			delete settings.data;
207			form.ajaxSubmit(settings);
208			return false;
209		});
210		$.rails.handleRemote(form);
211		return false;
212	});
213
214	// Any link with delete row, inside a table row, needs to delete that row if its post is successful (empty data)
215	$("tr a.delete-row").on("ajax:success", function(event, data) {
216		if (data.length == 0)
217			$(this).closest("tr").remove();
218		else
219			alert(data);
220	});
221
222	// Select the contents of an input field on focus if requested
223	$(":input.select-on-focus").focus(function() {
224		$(this).select();
225	});
226
227	// pop open a Facebox
228	$("a.facebox:not([data-remote])").livequery(function() {
229		var e = this;
230
231		// Before loading facebox, set width determined by data-facebox-width attribute
232		$(document).unbind("beforeReveal.facebox").bind("beforeReveal.facebox", function() {
233			// read data-facebox-width or default to value in jquery.facebox.css
234			var width = $(e).data("facebox-width");
235			if (width != null && width.toString().indexOf("%") != -1)
236				width = $(document).width() * parseInt(width) / 100;
237			if (width != null)
238				$("#facebox div.content").css("width", width);
239		});
240
241		$(this).facebox();
242	});
243
244	// Use .facebox to open remote links in faceboxes
245	$(document).on("ajax:beforeSend", "a.facebox[data-remote],a.facebox[remote],form.facebox[data-remote]", function(event,settings) {
246		var faceboxOpener = this;
247
248		$("#facebox .body div.content").is(":visible") ? appendLoadingMask($("#facebox .body div.content")) : $.facebox.loading();
249		var ajaxUrl = $(this)[0].tagName == 'A' ? $(this).attr("href") : '';
250		if (ajaxUrl.length == 0)
251			$("#facebox .reload").hide().unbind("click");
252		else
253			$("#facebox .reload").show().unbind("click").bind("click", function() {$.facebox({ajax:ajaxUrl});});
254
255		// If this link has a facebox-close callback on it remove any current ones and set this one up
256		if ($(this).data("facebox-close") != null)
257			$(document).unbind("afterClose.facebox").bind("afterClose.facebox", function() {eval($(faceboxOpener).data("facebox-close"));});
258
259	}).on("ajax:success", "a.facebox[data-remote],a.facebox[remote],form.facebox[data-remote]", function(event, data, status, xhr) {
260
261		// If we get some data go ahead and show it in the facebox
262		if (data.length > 0 && (xhr.getAllResponseHeaders() || '').indexOf("text/javascript") == -1) {
263			var e = this;
264
265			// Before loading facebox, set width determined by data-facebox-width attribute
266			$(document).unbind("beforeReveal.facebox").bind("beforeReveal.facebox", function() {
267				// read data-facebox-width or default to value in jquery.facebox.css
268				var width = $(e).data("facebox-width");
269				if (width != null && width.toString().indexOf("%") != -1)
270					width = $(document).width() * parseInt(width) / 100;
271				if (width != null)
272					$("#facebox div.content").css("width", width);
273			});
274
275			// Display facebox
276			$.facebox(data);
277
278		// Close facebox if empty data is returned
279		} else if (data.length == 0) {
280			$(document).trigger("close.facebox");
281		}
282	});
283
284	// set page reload callback for ajax forms
285	$("form.callback-reload[data-remote],a.callback-reload[data-remote]").on("ajax:success", function(event, data) {
286		if (data.length == 0) {
287			$(document).trigger('close.facebox');
288			window.onbeforeunload = null;
289			window.location.reload();
290		}
291	});
292
293	// make page focus on flash errors
294	$(".flash-err").livequery(function() {
295		var viewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0),
296			errorPoint = $(this).offset().top,
297			flashArea = $(this).closest("flash-area");
298
299		if (errorPoint < window.pageYOffset) {
300			$(window).scrollTop(errorPoint - 75);
301		} else if (errorPoint > window.pageYOffset + viewportHeight) {
302			var numScreensDown = Math.floor(errorPoint / viewportHeight);
303			var remainingPixels = errorPoint % viewportHeight;
304			$(window).scrollTop((numScreensDown * viewportHeight) + (remainingPixels / 2));
305		}
306	});
307
308	// Make flash messages be modals when they're generated by ajax calls.
309	// $(".table-listing td a").livequery(function() {
310	// 	$(this).click(function() {
311	// 		$showFlashMessage($(this), "link");
312	// 	});
313	// });
314	// $(".table-listing td input").livequery(function() {
315	// 	$(this).change(function() {
316	// 		$showFlashMessage($(this), "input");
317	// 	});
318	// });
319	// function $showFlashMessage(thisArg, type) {
320	// 	$(".flash-area").appendTo('html')
321	// 	if (type == "link") {
322	// 		if (!$(thisArg).hasClass('linked-icon-delete'))
323	// 			listingRecordNumber = $(thisArg).parent().siblings('td.numbered').html();
324	// 	} else if (type == "input") {
325	// 		listingRecordNumber = $(thisArg).closest("form").parent().siblings('td.numbered').html();
326	// 	}
327	// 	$(".listing-flash-highlight").each(function() {
328	// 		$(".listing-flash-highlight").removeClass('listing-flash-highlight').removeClass('err').removeClass('success');
329	// 	});
330	// }
331	// $(document).ajaxStop(function() {
332	// 	$(".flash-area.modal-flash").each(function() {
333	// 		if ($(this).closest('.table-listing').length == 0) {
334	// 			var flashMessage = $(".flash-area");
335	// 			if (flashMessage.html() != "") {
336	// 				var htmlToAppend = `<div class="temp-flash"><div class="flash-area">${flashMessage.html()}</div></div>`;
337	// 				$("body").after(htmlToAppend);
338	// 				flashMessage.removeClass().addClass('flash-area').html("");
339	// 			}
340	// 			$("td.numbered").each(function() {
341	// 				if ($('.table-listing').length > 0 && typeof listingRecordNumber !== 'undefined' && listingRecordNumber != "" && $(this).html() == listingRecordNumber) {
342	// 					var currentRow = $(this).parent();
343	// 					currentRow.addClass('listing-flash-highlight');
344	// 					currentRow.addClass(flashMessage.children('div').hasClass('flash-err') ? 'err' : 'success');
345	// 				}
346	// 			});
347	// 		}
348	// 	});
349	// });
350	// $(".temp-flash").livequery(function() {
351	// 	$(this).fadeIn(300, function() {
352	// 		var durationMultiplier = Math.ceil($(this).find(".flash-area").text().length / 150);
353	// 		if (durationMultiplier < 1)
354	// 			durationMultiplier = 1;
355	// 		$(this).delay(2500 * durationMultiplier).fadeOut(300, function() {
356	// 			$(this).remove();
357	// 		});
358	// 	});
359	// 	delete listingRecordNumber;
360	// });
361
362	// use data-load-target to specify destination for remote content
363	$("a[data-load-target], form[data-load-target]").livequery("ajax:beforeSend", function(event, settings) {
364		var theTarget = $($(this).data("load-target"));
365		if (!$(theTarget).data("pre-load"))
366			$(theTarget).data("pre-load", $(theTarget).html());
367		appendLoadingMask(theTarget);
368	}).livequery("ajax:success", function(event, data, status, xhr) {
369		if (data.length > 0 && (xhr.getAllResponseHeaders() || '').indexOf("text/javascript") == -1) {
370			if ($(this).data("replace-target") != null && $(this).data("replace-target") == true)
371				$($(this).data("load-target")).replaceWith(data);
372			else
373				$($(this).data("load-target")).html(data);
374		}
375	});
376
377	// use data-revert-load-target on links or buttons to revert to the previous load target content on click.
378	// the element does not have to specify data-load-target if it is contained in an element that does.
379	$(document).on("click", "a[data-revert-load-target], input[data-revert-load-target]", function(event) {
380		var targetSelector = $(this).data("load-target") || $(this).closest("[data-load-target]").data("load-target");
381		var target = $(targetSelector);
382		if (target.length > 0)
383			target.html(target.data("pre-load") || "");
384		return stopEvent(event);
385	});
386
387	// use data-empty-target on links or buttons to clear a targets contents on click
388	$(document).on("click", "a[data-empty-target], input[data-empty-target]", function(event) {
389		var target = $($(this).data("empty-target"));
390		if (target.length > 0)
391			target.html("");
392		return stopEvent(event);
393	});
394
395	// pop open a Facebox with a button
396	$(":button.facebox[rel], :submit.facebox[rel]").livequery(function() {
397		$(this).click(function() {
398			$.facebox({ajax: $(this).attr("rel")});
399		});
400	});
401
402	// close a facebox on click
403	$(".close-facebox").livequery("click", function(event) {
404		if (!$(this).attr("disabled")) {
405			$(document).trigger('close.facebox');
406			if (!$(this).hasClass("input-submit"))
407				return stopEvent(event);
408			else
409				return true;
410		}
411	});
412
413	// cause an item to disappear in 1.5 seconds
414	$(".fade-out").livequery(function() {
415		setTimeout(function(e) {$(e).remove();}, 1500, [this]);
416	});
417
418	// cause a form field to be all caps on blur
419	$(".make-caps").livequery("blur", function() {
420		$(this).val($(this).val().toUpperCase());
421
422	// make-caps isn't firing the blur or change if enter is hit, manually bind it and submit the form
423	}).livequery("keydown", function(event) {
424		var key = event.which ? event.which : event.keyCode;
425		if (key == KEYENUM.RETURN) {
426			if (!$(this).hasClass("stop-enter-key")) {
427				$(this).blur().change();
428				if ($(this.form) !== undefined)
429					$(this.form).submit();
430			}
431			return stopEvent(event);
432		}
433	});
434
435	// Bind sortables
436	$("ul.sortable, .sortable[data-sortableelementsselector]").livequery(function() {
437		var connectToAny = $(this).hasClass("connect-to-any");
438		var sortableElementsSelector = $(this).data("sortableelementsselector") | "li";
439		$(this).sortable({
440			connectWith: !connectToAny ? $(this).siblings('.sortable:not(.do-not-connect)') : ".sortable:not(.do-not-connect)",
441			items: "> *:not(.locked)"
442		});
443
444		// Update any sort elements with a span of .option-index
445		$(this).bind("sortupdate", function() {
446			$(".sortable > " + sortableElementsSelector).each(function() {
447				if ($(this).has("span.option-index")) {
448					var spanContent = trim($(this).find("span.option-index").html()),
449						isAlphaContent = /^[a-zA-Z]$/.test(spanContent),
450						siblingLis = $(this).closest(".sortable").siblings(".sortable:not(.do-not-connect)").find("> " + sortableElementsSelector),
451
452						// If this list is only connecting to siblings we need to index against all sibling sort element tags only, otherwise, all of them.
453						index = !connectToAny ? siblingLis.add($(this).closest(".sortable").find("> " + sortableElementsSelector)).index(this) : $(this).index(".sortable > " + sortableElementsSelector);
454
455					// Specifying the original selector again on the index call returns the index relative to the whole set, not the containing element.
456					if (isAlphaContent)
457						$("span.option-index", this).html(spanContent.replace(/^[a-zA-Z]+/, String.fromCharCode(String(index + 1).charCodeAt(0) + 16)));
458					else
459						$("span.option-index", this).html(spanContent.replace(/^[0-9]+/, index + 1));
460				}
461			});
462		})
463		.bind("sortstart",function() {
464			$(".sortable > " + sortableElementsSelector).each(function() {
465				if ($(this).has("span.context-menu") && $("div.context-menu",this).is(":visible"))
466					$("span.context-menu", this).click();
467			});
468		});
469
470		// Bind the column balancing logic if requested
471		if ($(this).data("balance-columns")) {
472
473			// Re-balance columns on sortstop
474			$(this).bind("sortstop", function() {
475
476				// Find all sortables on the page that are connected
477				var sortablesToBalance = !connectToAny ? $(this).siblings('.sortable:not(.do-not-connect)').add(this) : $(".sortable:not(.do-not-connect)");
478				$(sortablesToBalance).each(function() {
479					// Variables we need
480					var nextSortable = $(this).next(".sortable:not(.do-not-connect)"),
481						balanceAt = $(this).data("balance-columns"),
482						excessiveOptions = $(this).find("> " + sortableElementsSelector + ":gt(" + (balanceAt - 1) + ")"),
483						numOptions = $(this).find("> " + sortableElementsSelector).length;
484
485					// This column has too many options. Move them to the next column.
486					if (numOptions > balanceAt) {
487						if ($(excessiveOptions).length > 0)
488							$(excessiveOptions).detach().prependTo(nextSortable);
489
490					// This column has too few options. Grab X amount from the next column.
491					} else if (nextSortable != null) {
492						var neededElements = $(nextSortable).find("> " + sortableElementsSelector + ":lt(" + (balanceAt - numOptions) + ")");
493
494						if ($(neededElements).length > 0)
495							$(neededElements).detach().appendTo(this);
496					}
497				});
498			});
499		}
500	});
501
502	// Stop submission on the enter key
503	$("input.stop-enter-key").livequery('keydown', function(e) {
504		// stop the event
505		if ((e.keyCode ? e.keyCode : e.which) == KEYENUM.RETURN)
506			return stopEvent(e);
507	});
508
509	// Add special functionality to the enter key
510	$("input.move-on-enter-key").livequery('keydown', function(e) {
511		var key = e.keyCode ? e.keyCode : e.which;
512
513		// find the next input field
514		var inputs = $(":input:visible");
515		var next = $(inputs).get(inputs.index(this) + 1);
516
517		// as long as we hit the enter key, are not on a button, and have a next field
518		if (key == 13 && !$(this).is(":button, :submit, :reset") && !(next === undefined)) {
519
520			// focus
521			// NOTE: this must be in a timeout block. alerts in click() will break stopEvent
522			setTimeout(function() {
523				$(next).focus();
524			}, 0);
525		}
526
527		// stop the event
528		if (key == 13)
529			return stopEvent(e);
530	});
531
532	// Add special functionality to the backspace key
533	$(":input:not(:text,:password,textarea)").not("[type='number']").livequery('keydown', function(e) {
534		if ((e.keyCode ? e.keyCode : e.which) == 8)
535			return stopEvent(e);
536	});
537
538	// Activate date selection boxes
539	$("input.input-date:not(:disabled):not([readonly])").livequery(function() {
540		var options = {};
541		// Look for possible options coming in
542		if ($(this).data("min-date") != null)
543			options.minDate = $(this).data("min-date");
544		if ($(this).data("max-date") != null)
545			options.maxDate = $(this).data("max-date");
546		$(this).data("dateSettings", options);
547		bindDatePicker(this);
548	});
549
550	// Activate datetime selection boxes
551	$("input.input-datetime:not(:disabled):not([readonly]), input.input-timepicker:not(:disabled):not([readonly])").livequery(function() {
552		var options = {
553			timeFormat: "hh:mm TT",
554			addSliderAccess: true,
555			sliderAccessArgs: { touchonly: false }
556		};
557
558		// If this is timepicker, only show time
559		if ($(this).is(".input-timepicker"))
560			options.timeOnly = true;
561
562		// Look for possible options coming in
563		if ($(this).data("min-date") != null)
564			options.minDate = $(this).data("min-date");
565		if ($(this).data("max-date") != null)
566			options.maxDate = $(this).data("max-date");
567		if ($(this).data("min-hour") != null)
568			options.hourMin = $(this).data("min-hour");
569		if ($(this).data("max-hour") != null)
570			options.hourMax = $(this).data("max-hour");
571		if ($(this).data("min-minute") != null)
572			options.minuteMin = $(this).data("min-minute");
573		if ($(this).data("max-minute") != null)
574			options.minuteMax = $(this).data("max-minute");
575		if ($(this).data("step-hour") != null)
576			options.stepHour = $(this).data("step-hour");
577		if ($(this).data("step-minute") != null)
578			options.stepMinute = $(this).data("step-minute");
579
580		$(this).data("datetimeSettings", options);
581		bindDatetimePicker(this);
582	});
583
584	// For date and datetime fields automatically insert the / characters as needed
585	$("input.input-date,input.input-datetime").livequery(function() {
586		// If the last character is a / and they try to enter one, stop it.
587		$(this).keydown(function(event) {
588			var key = event.which ? event.which : event.keyCode;
589			var currVal = $(this).val();
590			if (currVal[currVal.length - 1] == '/' && (key == KEYENUM.FORWARDSLASH || key == KEYENUM.NUMPADFORWARDSLASH))
591				return stopEvent(event);
592
593		// Setup some logic to automatically insert the / characters as necessary while they type
594		}).keyup(function(event) {
595			var key = event.which ? event.which : event.keyCode;
596			var currVal = $(this).val();
597
598			// After the 2nd and 5th characters insert a /
599			if (key != KEYENUM.FORWARDSLASH && key != KEYENUM.NUMPADFORWARDSLASH) {
600				if (currVal.length == 2 || currVal.length == 5)
601					$(this).val(currVal + '/');
602			}
603		});
604	});
605
606	// Format Percentage fields
607	$(":input.percent").livequery(function() {
608		$(this).val($.formatNumber($(this).val(), {format:numberFormats.decimal, locale:"us"}));
609		$(this).blur(function() {
610			$(this).val($.formatNumber($(this).val(), {format:numberFormats.decimal, locale:"us"}));
611		});
612	});
613
614	// enable inline labels for text fields
615	// this allows labels to appear in text boxes, not outside them
616	$("textarea.inline-label, input.input-text.inline-label").livequery(function() {
617		var meta = $(this).metadata();
618		if (!(meta.LABEL === undefined)) {
619			if ($(this).origVal() == '')
620				$(this).addClass("empty-field").origVal(meta.LABEL);
621			$(this).focus(function() {
622				if ($(this).hasClass("empty-field"))
623					$(this).removeClass("empty-field").origVal('');
624			});
625			$(this).blur(function() {
626				if ($(this).origVal() == '')
627					$(this).addClass("empty-field").origVal(meta.LABEL);
628			});
629		}
630	});
631
632	// enable editable-data input toggling
633	$(":input.facade-field").livequery(function() {
634		var field = this;
635		var data = $(this).next("span.facade-data");
636		var oldVal = $(this).val();
637		var origVal = oldVal;
638		if ($(data).length > 0) {
639			$(data)
640				.click(function() {
641					$(data).addClass("hidden");
642					$(field).removeClass("hidden").focus().select();
643					return false;
644				})
645				.focus(function() {
646					$(this).click();
647				});
648			$(field)
649				.change(function() {
650					var val = $(field).val();
651					$(data).html(val);
652					if (val != "")
653						oldVal = val;
654				})
655				.blur(function() {
656					if ($(field).val() == "") {
657						$(field).val(oldVal);
658						$(data).html(oldVal);
659					}
660					if ($(field).val() != "") {
661						$(field).addClass("hidden");
662						$(data).removeClass("hidden");
663					}
664				})
665				.keydown(function(e) {
666					var key = e.which | e.keyCode;
667					if (key == KEYENUM.ESC) {
668						$(field).val(origVal);
669						$(data).html(origVal);
670						oldVal = origVal;
671						$(field).blur();
672					}
673				});
674		}
675	});
676
677	$(":input.facade-field").livequery(function() {
678		$(this).blur(function() {
679			// update the contents
680		});
681	});
682
683	// focus on the focus field
684	$(":input.focus,a.focus").livequery(function() {
685		// Make sure we are not stealing focus from a visible facebox element and
686		// Make sure we are focusing on the element if it is in a visible facebox
687		if (($(this).closest('#facebox').length == 0 && !$("#facebox").is(":visible")) || ($(this).closest('#facebox').length > 0 && $("#facebox").is(":visible")))
688			$(this).focus();
689	});
690
691	// If there is no visible focus field, set focus on the first visible input - Do it in a 1ms time out so that DOM gets loaded
692	setTimeout(function() {
693		if ($(":input:visible.focus,a:visible.focus").length == 0)
694			$("div#body :input:visible:first").focus();
695	}, 1);
696
697	// If there is no visible focus field on the facebox, set focus on the first visible input in the facebox - Do it in a 1ms time out so that DOM gets loaded
698	$(document).bind('reveal.facebox', function() {
699		setTimeout(function() {
700			if ($("#facebox :input:visible.focus,a:visible.focus").length == 0)
701				$("#facebox :input:visible:first").focus();
702		}, 1);
703		return true;
704	});
705
706	$("p span[class*='toggle-text-length-'],div.table-listing tr td span[class*='toggle-text-length-']").livequery(function() {
707		var maxLen = $(this).attr('class').replace(/^.*toggle-text-length-(\d+).*$/, '$1');
708		var text = $(this).html().replace(/<(.|\n|\r)*?>/g,'');
709
710		// if the length of html exceeds our maximum length...
711		if (text.length > maxLen) {
712			var regex = new RegExp("^(.|\\n|\\r){" + maxLen + "}\\w{0,15}");
713			maxLen = regex.exec(text)[0].length;
714
715			$(this).html(text.substr(0, maxLen) + ' <span style="display: none">' + text.substr(maxLen) + '</span> <a class="show-more">... [more]</a>');
716			$("a.show-more", this).click(function() {
717				var span = $(this).prev("span");
718				$(this).html($(span).is(":visible") ? "... [more]" : "[less]");
719				$(span).slideToggle(400);
720				return false;
721			});
722		}
723	});
724
725	$("a.toggle-all-text-length").on("click", function(event) {
726		$("a.show-more").click();
727		$(this).html($(this).html().trim() == "More" ? "Less" : "More");
728		return stopEvent(event);
729	});
730
731	// Active html editor boxes
732	$("textarea.input-html").livequery(function() {
733		if ($(this).parent("div.rte-zone").length == 0) {
734			$(this).rte({
735				controls_rte: {
736				    s1: {
737				        separator: true
738				    },
739				    bold: {
740				        command: "bold",
741				        tags: ["b", "strong"]
742				    },
743				    italic: {
744				        command: "italic",
745				        tags: ["i", "em"]
746				    },
747				    underline: {
748				        command: "underline",
749				        tags: ["u"]
750				    },
751				    s2: {
752				        separator: true
753				    },
754				    justifyLeft: {
755				        command: "justifyleft"
756				    },
757				    justifyCenter: {
758				        command: "justifycenter"
759				    },
760				    justifyRight: {
761				        command: "justifyright"
762				    },
763				    justifyFull: {
764				        command: "justifyfull"
765				    },
766				    s3: {
767				        separator: true
768				    },
769				    subscript: {
770				        command: "subscript",
771				        tags: ["sub"]
772				    },
773				    superscript: {
774				        command: "superscript",
775				        tags: ["sup"]
776				    },
777				    s5: {
778				        separator: true
779				    },
780				    orderedList: {
781				        command: "insertorderedlist",
782				        tags: ["ol"]
783				    },
784				    unorderedList: {
785				        command: "insertunorderedlist",
786				        tags: ["ul"]
787				    },
788				    s6: {
789				        separator: true
790				    },
791				    block: {
792				        command: "formatblock",
793				        select: '<select>	<option value="">- format -</option>	<option value="<p>">Paragraph</option>	<option value="<h1>">Header 1</option>	<option value="<h2>">Header 2</options>	<option value="<h3>">Header 3</option>	<option value="<h4>">Header 4</options>	<option value="<h5>">Header 5</option>	<option value="<h6>">Header 6</options></select>	',
794				        tag_cmp: lwrte_block_compare,
795				        tags: ["p", "h1", "h2", "h3", "h4", "h5", "h6"]
796				    },
797				    link: {
798				        exec: lwrte_link,
799				        tags: ["a"]
800				    },
801				    unlink: {
802				        command: "unlink"
803				    },
804				    s8: {
805				        separator: true
806				    },
807				    removeFormat: {
808				        exec: lwrte_unformat
809				    },
810				    clear: {
811				        exec: lwrte_clear
812				    }
813				},
814				controls_html: {}
815			});
816		}
817	});
818
819	// Automatically post on change
820	$(":input.submit-on-change").livequery(function() {
821		$(this).change(function() {
822			$(this).blur();
823			$(this.form).submit();
824		});
825	});
826
827	$("form a.submit-on-click").on("click", function(event) {
828		$(this).closest("form").submit();
829		return stopEvent(event);
830	});
831
832	$(":input.highlight-row-on-change").livequery(function() {
833		$(this).change(function() {
834			$(this).closest("tr").addClass("unsaved-row");
835		});
836	});
837
838	// Turn on autocomplete features
839	$(":input.autocomplete").livequery(function() {
840		var name = $(this).attr("name");
841
842		// if item is a selectbox, use ui-combobox
843		if ($(this).is("select")) {
844			$(this).combobox();
845
846		// otherwise, use ui-autocomplete with some tweaking
847		} else {
848
849			// remove any hidden fields after the element
850			$(this).nextAll("input.autocomplete-hidden").remove();
851
852			// change name of original field
853			if (/^autocomplete\b/.test(name))
854				name = name.replace(/^autocomplete\[([^\]]+)\]/, "$1");
855			else
856				$(this).attr("name", name.replace(/^([^\[]+)/, "autocomplete[$1]"));
857
858			// create hidden field for value
859			var hidden = $("<input type='hidden' />")
860				.attr("name", name)
861				.attr("data-field-name", $(this).data("field-name") + "-hidden")
862				.addClass("autocomplete-hidden")
863				// add any classes set up on data-autocomplete-class attribute to this hidden field
864				.addClass($(this).data("autocomplete-class") != null ? $(this).data("autocomplete-class") : '')
865				.insertAfter(this);
866
867			// grab variables for processing
868			var url = $(this).data("autocomplete-url"),
869				field = this;
870
871			// function to look up a value and set it if valid
872			var lookup = function(val) {
873				if (val == "") {
874					$(hidden).add(field).val('');
875					return;
876				}
877				$.ajax({
878					url: url,
879					data: "id=" + val,
880					dataType: "json",
881					type: "get",
882					success: function(data) {
883						if (data.length > 0) {
884							$(hidden).val(data[0].id);
885							// The blur below causes us to lose focus on the current focused input.
886							var currentFocus = $(":input:focus");
887
888							// Hack to correctly change value
889							$(field)
890								.autocomplete("disable")
891								.val(data[0].value)
892								.blur()
893								.autocomplete("enable");
894							$(field).add(hidden).trigger("autocomplete:lookup");
895
896							// If we had a focused input return the focus and select its contents
897							if ($(currentFocus).length > 0)
898								$(currentFocus).focus().select();
899						} else if (!$(field).hasClass("allow-invalid-selection")) {
900							$(hidden).add(field).val('');
901						}
902					}
903				});
904			};
905
906			// TODO: don't just store this method in the element data
907			$(this).data("lookup", lookup);
908
909			// add autocomplete functionality
910			// TODO: multiple selection for textarea
911			$(this).autocomplete({
912				source: url,
913				autoFocus: false,
914				delay: 500,
915				select: function(event, ui) {
916					// If we are selecting a "more" option, do not actually change anything. Still fire the selected function
917					if (ui.item.id != "autocomplete-more") {
918						$(hidden).val(ui.item.id).change();
919					} else {
920						$(this).trigger("moreSelected");
921					}
922				},
923				change: function(event, ui) {
924					// If this autocomplete allows invalid selections go ahead and copy the value down to the hidden field
925					if ($(field).hasClass("allow-invalid-selection"))
926						$(hidden).val($(field).val()).change();
927					// Otherwise, empty the two fields out
928					else if (!ui.item) $(field).add(hidden).val('').change();
929				}
930			});
931
932			// When the field can accept invalid selections we need the enter key to pass the value down to the hidden field before the submit triggers
933			if ($(field).hasClass("allow-invalid-selection")) {
934
935				$(field).blur(function() {
936					$(hidden).val($(field).val());
937				});
938
939				$(field).keypress(function(event) {
940					var key = event.which ? event.which : event.keyCode;
941					if (key == KEYENUM.RETURN)
942						$(field).blur();
943				});
944			}
945
946			// perform reverse lookup to correctly fill value
947			lookup($(this).val());
948		}
949	});
950
951	// Place the populatedFrom code after autocomplete code to bind the autocomplete fields too
952	// Turn on populatedFrom features
953	$(":input.populated-from").livequery(function() {
954		// store this field
955		var populateField = this;
956		var withinAssociation = $(this).closest("div.ui-association").length;
957		var closestRow = $(this).closest("div.row");
958
959		// look up hidden (value) field
960		var hidden = $(this).parent().find(":input.populated-from-hidden")[0];
961
962		// store the different populated-from values in an array to loop over
963		var hiddenNames = $(hidden).metadata().POPULATEDFROM.split(",");
964
965		$.each(hiddenNames, function(i) {
966			// look up the field that triggers the population
967			if (withinAssociation)
968				var controlField = (/^#/.test(hiddenNames[i]) ? $(hiddenNames[i]) : $(":input[name*='" + hiddenNames[i] + "']:not([name*='populated-from'])", closestRow).not(this));
969			else
970				var controlField = (/^#/.test(hiddenNames[i]) ? $(hiddenNames[i]) : $(":input[name='" + hiddenNames[i] + "']:not([name*='populated-from'])").not(this));
971
972			// bind the change event on the controlFields
973			$(controlField).change(function(event) {
974
975				// Grab the current value for this field
976				var val = $(populateField).val();
977				var url = $(hidden).metadata().POPULATEBINDING;
978
979				// for each field/value, build our dataObject
980				var dataObject = {};
981
982				$.each(hiddenNames, function(j) {
983					if (hiddenNames.length > 1)
984						bindValue = "bindValue" + (j+1);
985					else
986						bindValue = "bindValue";
987
988					if (withinAssociation)
989						dataObject[bindValue] = (/^#/.test(hiddenNames[j]) ? $(hiddenNames[j]).val() : $(":input[name*='" + hiddenNames[j] + "']:not([name*='populated-from'])", closestRow).not(this).val());
990					else
991						dataObject[bindValue] = (/^#/.test(hiddenNames[j]) ? $(hiddenNames[j]).val() : $(":input[name='" + hiddenNames[j] + "']:not([name*='populated-from'])").not(this).val());
992				});
993
994				// Sometimes we may want to programatically prevent the populate-from execution
995				$(populateField).data("abortPopulate", false).data("populated", false);
996
997				// Run the populate:before action if it is present
998				$(populateField).trigger("populate:before");
999
1000				// Check if we need to abort the execution on this run
1001				if ($(populateField).data("abortPopulate")) {
1002					$(populateField).data("populated", true);
1003					return stopEvent(event);
1004				}
1005
1006				// pass the controlField value up to the populateBinding and populate our select field
1007				$.ajax({
1008					type: $(hidden).metadata().POPULATEMETHOD,
1009					url: url,
1010					data: dataObject,
1011					success: function(data) {
1012						// If we have a select field drop the options and append the new ones
1013						if ($(populateField).prop("tagName") == 'SELECT') {
1014							$("option:gt(0)", populateField).remove();
1015							if ($(populateField).hasClass("uses-generated-value"))
1016								$(populateField).append(data);
1017							else
1018								$(populateField).append(data).val(val);
1019
1020						// Otherwise just expect a single value back and drop it into the field
1021						} else {
1022							$(populateField).val(data);
1023						}
1024
1025						// In some cases we need to call .change() on the populated field because other fields populate off of it
1026						if ($(populateField).hasClass("change-populate-field-on-populate")) {
1027							$(populateField).change();
1028						}
1029
1030						$(populateField).data("populated", true).trigger("populate:after");
1031					}
1032				});
1033			});
1034
1035			// In some cases, we don't want .change() to be called on control field. In these cases, pass in the do-not-change class below.
1036			if (!$(controlField).hasClass("do-not-change-populate-from-on-load")) {
1037				$(controlField).change();
1038			}
1039		});
1040	});
1041
1042	// Duplicate values from one field to another
1043	$(":input[data-copy-value-from]").livequery(function() {
1044		var copyFromValue = $(this).attr("data-copy-value-from");
1045		var theForm = $(this).closest("form");
1046		var targetField = $(this);
1047		var sourceField = $(theForm).find(copyFromValue);
1048
1049		$(sourceField).change(function() {
1050			$(targetField).val($(sourceField).val());
1051		}).change();
1052	});
1053
1054	// enable fields when passed in checkbox is checked
1055	$(":input[data-enabled-from]").livequery(function() {
1056		var enabledFromValue = $(this).attr("data-enabled-from");
1057		var theForm = $(this).closest("form");
1058		var targetField = $(this);
1059		var sourceField = $(theForm).find(enabledFromValue);
1060
1061		$(sourceField).change(function() {
1062			if ($(sourceField).is(":checked") || !$(sourceField).is(":checkbox") && $(sourceField).val() != "")
1063				$(targetField).attr("disabled", false);
1064			else
1065				$(targetField).attr("disabled", true);
1066		}).change();
1067	});
1068
1069	// disable fields when passed in checkbox is unchecked
1070	$(":input[data-disabled-from]").livequery(function() {
1071		var disabledFromValue = $(this).attr("data-disabled-from");
1072		var theForm = $(this).closest("form");
1073		var targetField = $(this);
1074		var sourceField = $(theForm).find(disabledFromValue);
1075
1076		$(sourceField).change(function() {
1077			if ($(sourceField).is(":checked") || !$(sourceField).is(":checkbox") && $(sourceField).val() != "")
1078				$(targetField).attr("disabled", true);
1079			else
1080				$(targetField).attr("disabled", false);
1081		}).change();
1082	});
1083
1084	// disable fields when passed in checkbox is unchecked
1085	$(":checkbox[data-checked-from]").livequery(function() {
1086		var checkedFromValue = $(this).attr("data-checked-from");
1087		var theForm = $(this).closest("form");
1088		var targetField = $(this);
1089		var sourceField = $(theForm).find(checkedFromValue);
1090
1091		$(sourceField).change(function() {
1092			if ($(sourceField).is(":checked"))
1093				$(targetField).attr("checked", true);
1094			else
1095				$(targetField).attr("checked", false);
1096		});
1097	});
1098
1099	// Animate panel menus
1100	$("li:has(> ul.sub-menu)").livequery(function() {
1101		$(this).hover(function() {
1102			$("> ul.sub-menu", this).show();
1103		}, function() {
1104			$("> ul.sub-menu", this).hide();
1105		});
1106	});
1107
1108	// Force confirmation on delete boxes
1109	$("a.confirm").livequery(function() {
1110		var titleText = $(this).attr("title");
1111		$(this).click(function() {
1112			var message = titleText;
1113			if (message.length < 5)
1114				message = "Are you sure you want to perform this action?";
1115			return confirm(message);
1116		});
1117	});
1118
1119	// Throw the person back one page
1120	$("a.return-link").livequery(function() {
1121		$(this).click(function() {
1122			history.back();
1123			return false;
1124		});
1125	});
1126
1127	// Add tool-tips to jMonthCalendar events
1128	$("#jMonthCalendar div.Event a").livequery(function() {
1129		$(this).attr('title', $(this).html());
1130	});
1131
1132	// Add confirmation for mailto addresses
1133	$("a.mailto").livequery(function() {
1134		$(this).click(function() {
1135			var email = this.href.replace(/^mailto:\/*/, "");
1136			return confirm("Send email to " + email + "?");
1137		});
1138	});
1139
1140	// Put tool-tips on links with titles
1141	$("a[title]:not(.no-tooltip)").livequery(function () {
1142		// HACK: Since 'tooltip' removes the title from this element, save it on the 'saved-title' attribute for future reference
1143		$(this).attr("saved-title", $(this).attr("title"));
1144		$(this).tooltip({track:true});
1145	});
1146
1147	// put a tooltip on any element we specify
1148	$(".show-tooltip[title]").livequery(function() {
1149		// HACK: Since 'tooltip' removes the title from this element, save it on the 'saved-title' attribute for future reference
1150		$(this).attr("saved-title", $(this).attr("title"));
1151		$(this).tooltip({track:true});
1152	});
1153
1154	// Assign ajax tooltip to every element that is matched
1155	$(".ajax-tooltip[rel!='']").livequery(function() {
1156		var e = this;
1157		// HACK: Since 'tooltip' removes the title from this element, save it on the 'saved-title' attribute for future reference
1158		$(this).attr("saved-title", $(this).attr("title"));
1159		$(this).tooltip({
1160			track: true,
1161			content: function() {
1162				var d = $(e).data('tooltip-cache');
1163				if (!(d === undefined) && d.length > 0)
1164					return d;
1165				$.ajax({
1166					async: false,
1167					url: $(e).attr('rel'),
1168					success: function(data){
1169						d = data;
1170					}
1171				});
1172				$(e).data('tooltip-cache', d);
1173				return d;
1174			}
1175		});
1176	});
1177
1178	// Load contents into a facebox on hover
1179	$(".hover-facebox[rel!='']").livequery(function() {
1180		var timeout = null;
1181		$(this).hover(
1182			function(){
1183				timeout = setTimeout(function(e){$.facebox({ajax: $(e).attr("rel")})}, 1000, [this]);
1184			},
1185			function(){clearTimeout(timeout)});
1186	});
1187
1188	// Setup ajax-load-targets
1189	$("a.ajax-load-target[rel]").livequery(function() {
1190		$(this).click(function(event) {
1191
1192			// find the object we're loading into
1193			var loadTarget = $("#" + $(this).attr("rel"));
1194
1195			// if we found one, load it.
1196			if ($(loadTarget).length > 0) {
1197				$(loadTarget).load($(this).attr("href"));
1198			}
1199
1200			// kill the click event
1201			return stopEvent(event);
1202		});
1203	});
1204
1205	// Run left-right assigners
1206	$("div.assigner-container").livequery(function() {
1207		$(this).assigner();
1208	});
1209
1210	// handle regular forms
1211	// IMPORTANT: this must be ran before all other .submit() binds!
1212	$("form").livequery(function() {
1213
1214		// turn on form validation
1215		$(this).validate();
1216
1217		// handle form submission AFTER VALIDATION!
1218		$(this).submit(function() {
1219
1220			// clean up fields with inline-labels
1221			$(":input.empty-field", this).origVal("");
1222		});
1223	});
1224
1225	// handle ajax-forms
1226	$("form.ajax-form").livequery(function() {
1227		if ($(this).attr('data-load-target') != null)
1228			var div = $("#" + $(this).attr('data-load-target'));
1229		else
1230			var div = $(this).closest('div')[0];
1231		var theForm = this;
1232		$(this).submit(function(event) {
1233
1234			// create and append the loading mask
1235			appendLoadingMask(div);
1236
1237			// post!
1238			$(this).ajaxSubmit({
1239				success: function(data){
1240					if ($(theForm).data("callback")) {
1241						$(theForm).data("callback")(data);
1242					}
1243
1244					if (div)
1245						$(div).html(data);
1246				}
1247			});
1248			return false;
1249		});
1250	});
1251
1252	// button link to cancel ajax form
1253	$("form.ajax-form a.cancel").livequery(function() {
1254		var p = $(this).closest('form');
1255		$(this).click(function() {
1256			$(p).remove();
1257			return false;
1258		});
1259	});
1260
1261	// post form via ajax and load into a facebox
1262	$("form.facebox-form").livequery(function() {
1263		$(this).submit(function() {
1264
1265			// perform submission
1266			$(this).ajaxSubmit({
1267				success: function(data){
1268					$.facebox(data);
1269				}
1270			});
1271			return false;
1272		});
1273	});
1274
1275	// set page reload callback for ajax forms
1276	$("form.callback-reload").livequery(function() {
1277		$(this).data("callback", function(data) {
1278			if (data.length == 0) {
1279				$(document).trigger('close.facebox');
1280				window.location.reload();
1281			}
1282		});
1283	});
1284
1285	// Run tabbed windows
1286	$("div.tab-container.dynamic").livequery(function() {
1287		$(this).tabset();
1288	});
1289
1290	// Set up tab forms
1291	$("div.tab-container.dynamic div.tab-content-item:not('.static') form").livequery(function() {
1292		$(this).tabsetForm();
1293	});
1294
1295	/* ajax load any div that comes in with a rel attribute and the correct class
1296		Only one object is loading at a time to reduce the amount of hits on the server and to provide the user with a less abrasive UI */
1297	var ajaxLoadRunning = false;
1298	$("div.ajax-on-load[rel],section.ajax-on-load[rel]").livequery(function() {
1299		var theDiv = this;
1300
1301		var load = function() {
1302			var ajaxLoadTimer = setInterval(function() {
1303				if (!ajaxLoadRunning) {
1304					ajaxLoadRunning = true;
1305					clearInterval(ajaxLoadTimer);
1306					ajaxLoadTimer = null;
1307
1308					appendLoadingMask(theDiv);
1309					$(theDiv).load($(theDiv).attr("rel"), function() {
1310						ajaxLoadRunning = false;
1311					});
1312				}
1313			}, 100);
1314		}
1315
1316		// Store a pointer to the load function so it can be refreshed as necessary
1317		$(this).data("reload", load);
1318		load();
1319	});
1320
1321	$("div.ajax-on-load-async[rel]").livequery(function() {
1322		appendLoadingMask(this);
1323		$(this).load($(this).attr("rel"));
1324	});
1325
1326	// table listings
1327	$("div.table-listing.dynamic").livequery(function() {
1328
1329		// elements and data
1330		var div = this;
1331		var meta = $(this).metadata();
1332		var filters = $('> div.table-header form.filter-fields', this)[0];
1333		var filterInputs = $('> div.table-header form.filter-inputs', this)[1];
1334		var queryField = $("#field-filter-query", div)[0] || null;
1335		var queryElement = $("li.query", filters);
1336		if ($(this).hasClass("has-filter-form")) {
1337			var additionalFieldsForm = $("form.additional-fields", div);
1338		} else {
1339			var additionalFieldsForm = $("form.additional-fields").not(function() {
1340
1341				// make sure that the fields form is adjacent to the table listing div
1342				var elements = $("form.additional-fields,div.table-listing");
1343				for (var i = 0; i < elements.length; i++)
1344					if (elements.eq(i).is(this) && elements.eq(i+1).is(div))
1345						return false;
1346				return true;
1347			});
1348		}
1349		var additionalFields = $(":input.listing-field", additionalFieldsForm);
1350		var sidebarForm = $("form.sidebar-fields");
1351		var sortFields = $("> div.table-header form.sort-fields", div);
1352		var element_sortQuery = $(this).find("#field-sort-query");
1353		if ($(sortFields).length > 0)
1354			var sortQuery = $("#field-sort-query", sortFields)[0];
1355		var bulkActions = $("> div.table-header a.bulk-action", div);
1356		var head = $("> table > thead", this);
1357		var headOffset = $(head).offset();
1358		var numberedCells = $("td.numbered, th.numbered", div);
1359		var sidebar = null;
1360		var sidebarOffset = 0;
1361		var fauxHead = null;
1362		var floatWithFauxHead = null;
1363
1364		// Modify listing id and, thereby, listingName for sessionParamsAndSavedReports call to be unique when listing is in a drill-down
1365		var parentDrillDownRow = $(div).closest("tr.edit-form-row").prev("tr");
1366		if ($(parentDrillDownRow).length == 1) {
1367
1368			// Disable the listing-default div if it exists. Functionality not supported on drill-down listings
1369			$("a.set-listing-defaults > img", div).attr("title", "Functionality not supported on drill-down listings");
1370			$("a.set-listing-defaults", div).replaceWith(function() {
1371				return $("img", this);
1372			});
1373
1374
1375			if ($(additionalFieldsForm).length == 0) {
1376
1377				// Find the rel string to parse through
1378				var relString = $("a.row-is-open", parentDrillDownRow).attr("rel").replace(/.*\?/, "");
1379				var paramsArray = relString.split("&");
1380
1381				if (paramsArray.length > 0) {
1382					$(div).before('<form class="additional-fields" action="#" method="post"></form>');
1383				}
1384
1385				// Create the additional fields form
1386				for (var i = paramsArray.length - 1; i >= 0; i--) {
1387					if (paramsArray[i].search("=") !== -1) {
1388						paramsArray[i] = decodeURIComponent(paramsArray[i]);
1389						$("form.additional-fields", $(div).closest("td")).append('<input type="hidden" class="input-hidden" id="field-additional-field-' + i + '" name="' + paramsArray[i].replace(/=.*/, "") + '" value=' + paramsArray[i].replace(/.*=/, "") + '>');
1390					}
1391				};
1392
1393				// We just created our additionalFieldsForm. Set it to the variable so everything else can use it.
1394				additionalFieldsForm = $("form.additional-fields", $(div).closest("td"));
1395			}
1396		}
1397
1398		// rebuild the value of the query from the spans
1399		var rebuildQuery = function() {
1400			var struct = $(queryElement).clone();
1401
1402			// fix IN operations
1403			$("> span", struct).each(function() {
1404				if ($("span.op", this).attr("rel") == "IN")
1405					$("span.val", this).html("(" + $("span.val", this).html().replace(/<[^>]+>/g, "") + ")");
1406			});
1407
1408			// remove links
1409			$("a", struct).remove();
1410
1411			// remove top spans, and replace inner spans with their contents or rels
1412			$("span.label", struct).unwrap();
1413			$("span", struct).replaceWith(function() {
1414				var rel = $(this).attr("rel");
1415				return (rel === undefined) ? $(this).html() : rel;
1416			});
1417
1418			// set new query value without redundant spaces
1419			if (queryField != null)
1420				$(queryField).val(trim($(struct).html().replace(/\s+/g, " ").replace(/\[|\]/g, "")));
1421		};
1422
1423		// format filter query string
1424		var formatQuery = function() {
1425			if (queryField != null) {
1426				var q = $(queryField).val();
1427				// wrap spans around: column names, operations, values, and logical operators
1428				q = q.replace(
1429					/(\w+)\s+([<>^*$=!]=|<|>|IN)\s+\(?((?:'[^']+'|\d+)\.?(?:,? *(?:'[^']+'|\d+))*|EMPTY)\)?(\W*)/g,
1430					'<span>[<span class="label" rel="$1">$1</span> <span class="op" rel="$2">$2</span> <span class="val">$3</span>]$4</span>'
1431				);
1432				q = q.replace(/((&amp;){2}|&&|\|{2})/g, '<span class="conn" rel="$1">$1</span>');
1433
1434				// set query html up
1435				$(queryElement).html(q);
1436
1437				// if the operator is an IN
1438				$("> span", queryElement).each(function() {
1439					if ($("span.op", this).attr('rel') == "IN") {
1440						var span = this;
1441						var val = $("span.val", this);
1442						var values = $(val).html().split(',');
1443						$(val).html("");
1444						for (var i = 0; i < values.length; i++) {
1445							$("<a href='#' />").html(trim(values[i])).click(function() {
1446								var prev = $(this).prev();
1447								if ($(this).siblings().length == 0) {
1448									$("span.label a", span).click();
1449								} else {
1450									if ($(this).is(":last-child"))
1451										$("span.comma", prev).remove();
1452									$(this).remove();
1453									rebuildQuery();
1454									refreshView(1);
1455								}
1456								return false;
1457							}).appendTo(val);
1458						}
1459						$("> a:not(:last-child)", val).append($("<span class='comma'>,</span>"));
1460					}
1461				});
1462
1463				// replace column names with appropriate labels
1464				$("span.label", queryElement).each(function(){
1465					var label = $("span[rel='" + $(this).attr('rel') + "'] label", filterInputs);
1466					if (label.length > 0)
1467						$(this).html(label.html());
1468				});
1469
1470				// replace operation symbols with appropriate words
1471				$("span.op,span.conn", queryElement).each(function() {
1472					var key = $(this).html();
1473					key = key.replace("==", "is");
1474					key = key.replace("!=", "is not");
1475					key = key.replace(/(<|&lt;)=/, "less than / equal");
1476					key = key.replace(/(<|&lt;)/, "less than");
1477					key = key.replace(/(>|&gt;)=/, "greater than / equal");
1478					key = key.replace(/(>|&gt;)/, "greater than");
1479					key = key.replace("*=", "contains");
1480					key = key.replace("^=", "starts with");
1481					key = key.replace("$=", "ends with");
1482					key = key.replace(/\bIN\b/, "in");
1483					key = key.replace("&&", "and");
1484					key = key.replace("&amp;&amp;", "and");
1485					key = key.replace("||", "or");
1486					$(this).html(key);
1487				});
1488
1489				// add links to remove filters
1490				$("> span", queryElement).each(function(){
1491					var span = this;
1492					var label = $("span.label", this).find("a").remove().end();
1493					var link = $("<a href='#' title='Click to remove' />").click(function(){
1494
1495						// remove previous logical operator if needed
1496						if ($("span.conn", span).length == 0)
1497							$(span).prev().find("span.conn").remove();
1498
1499						// call mouse-out to make hint bubble disappear
1500						$(this).mouseout();
1501
1502						// remove current span
1503						$(span).remove();
1504
1505						// rebuild query and refresh
1506						rebuildQuery();
1507						refreshView(1);
1508
1509						return false;
1510					});
1511
1512					$(label).html($(link).html($(label).html().trim()));
1513				});
1514			}
1515		};
1516
1517		// rebuild the sorting query from list items
1518		var rebuildSort = function() {
1519			if (sortQuery !== undefined) {
1520				var q = "";
1521				$("li.sort", sortFields).each(function() {
1522					var meta = $(this).metadata();
1523					var val = meta.SORTNAME;
1524					val += ($(this).hasClass("sort-desc")) ? " DESC:" : " ASC:";
1525					val += meta.SORTBY;
1526					if (q.length > 0)
1527						q += "|";
1528					q += val;
1529				});
1530				$(sortQuery).val(q);
1531			}
1532		};
1533
1534		// helper to send ajax requests
1535		var refreshView = function (page, afterRefresh, rowKey) {
1536
1537			// If we have additional fields form, validate everything in it
1538			if ($(additionalFieldsForm).length > 0) {
1539
1540				// Normal form validation
1541				if (!validate_form($(additionalFieldsForm)))
1542					return;
1543
1544				// Do not allow empty dates on additional fields when not permitted or dates exceeding the max date range
1545				if ($(additionalFieldsForm[0].dateSpan).length > 0) {
1546					if ($(additionalFieldsForm[0].dateSpan).data("allow-empty-dates") != null) {
1547						if (!$(additionalFieldsForm[0].dateSpan).data("allow-empty-dates") && (additionalFieldsForm[0].startDate.value == '' || additionalFieldsForm[0].endDate.value == '')) {
1548							alert("Both start and end dates must be defined for this listing.");
1549							return;
1550						}
1551					}
1552					if ($(additionalFieldsForm[0].dateSpan).data("max-date-range") != '') {
1553						if ((new Date(new Date(additionalFieldsForm[0].endDate.value) - new Date(additionalFieldsForm[0].startDate.value)))/(1000 * 3600 * 24) >= $(additionalFieldsForm[0].dateSpan).data("max-date-range")) {
1554							alert("Date range cannot be larger than " + $(additionalFieldsForm[0].dateSpan).data("max-date-range") + " days");
1555							return;
1556						}
1557					}
1558				}
1559			}
1560
1561			// Remove the per-page alert if present
1562			removePageAlertBox();
1563			//$("#alert-box-per-page").remove();
1564
1565			// Lookup the current page they are on, if they didn't pass anything
1566			if (page===undefined || page == null) {
1567				page = 1;
1568				if ($("ul.pagination li a.current", div).length > 0)
1569					page = $("ul.pagination li a.current", div).attr("rel");
1570				else if ($("ul.pagination li :input.page-selector", div).length > 0)
1571					page = $("ul.pagination li :input.page-selector", div).val();
1572			}
1573
1574			// grab page size (possibly from faux-thead, it also may not exist if paging is off)
1575			var perPage = $("#faux-thead input.per-page");
1576			if ($(perPage).length == 0)
1577				perPage = $("input.per-page", div).length > 0 ? $("input.per-page", div) : null;
1578
1579			// store the value of the perPage field
1580			var perPageVal = perPage == null ? 0 : $(perPage).val();
1581
1582			// grab page numbering (possibly from faux-thead, it also may not exist if paging is off)
1583			var numbered = $("#faux-thead input.numbered-rows");
1584			if ($(numbered).length == 0)
1585				numbered = $("input.numbered-rows", div).length > 0 ? $("input.numbered-rows", div) : null;
1586
1587			// store whether the numbered checkbox is checked
1588			var numberedChecked = numbered == null ? false : $(numbered).is(":checked");
1589
1590			// Handle invalid per-page values
1591			if (perPage !== null && (isNaN(perPageVal) || perPageVal < 1)) {
1592				$(perPage).focus().select();
1593				createPageAlertBox(perPage, "Per page value must be a positive number.");
1594			} else if ($("ul.pagination li :input.page-selector", div).length > 0 && (isNaN(page) || page < 1)) {
1595				$("ul.pagination li :input.page-selector", div).focus().select();
1596				createPageAlertBox($("ul.pagination li :input.page-selector", div), "Page value must be a positive number.");
1597			// Page and per-page values are ok..continue
1598			} else if (!isNaN(page) && !isNaN(perPageVal)) {
1599
1600				// create and append the loading mask
1601				if (rowKey === undefined || rowKey == null)
1602					appendLoadingMask(div);
1603
1604				// add page/perPage data
1605				var data = "page=" + parseInt(page) + "&perPage=" + perPageVal;
1606
1607				// add numbered rows choice
1608				if (numberedChecked)
1609					data += "&numberedRows=true";
1610
1611				// add sorting data
1612				if (sortQuery !== undefined && $(sortQuery).val().length > 0) {
1613					data += "&sortQuery=" + $(sortQuery).val();
1614				} else {
1615					var sortCol = $("th.sort-asc, th.sort-desc", head);
1616					if (sortCol.length > 0) {
1617						var aMeta = sortCol.metadata();
1618						var sortAsc = (sortCol.is('.sort-asc')) ? true : false;
1619						data += "&sortColumn=" + aMeta.SORTNAME + "&sortBy=" + aMeta.SORTBY + "&sortAsc=" + sortAsc;
1620					}
1621				}
1622
1623				// grab filter query string
1624				if ((queryField != null) && $(queryField).val().length > 0)
1625						data += "&" + $(queryField).serialize();
1626
1627				// grab additional filters
1628				if ($(":input", additionalFieldsForm).length > 0) {
1629					var extraData = $(additionalFieldsForm).serialize();
1630					if (extraData.length > 0)
1631						data += "&" + extraData;
1632				}
1633
1634				// grab sidebar filters
1635				if ($(":input", sidebarForm).length > 0) {
1636					var sidebarData = $(sidebarForm).serialize();
1637					if (sidebarData.length > 0)
1638						data += "&" + sidebarData;
1639				}
1640
1641				// Pass the rowKey up if requested
1642				if (rowKey !== undefined && rowKey != null)
1643					data += "&rowKey=" + rowKey;
1644
1645				// Add _method=GET to the data if the _method key does not exist yet - this ensures Coldroute can still find the properly formed URLs via the GET route search table
1646				if (!/_method=/i.test(data))
1647					data += "&_method=GET";
1648
1649				// post the data and refresh
1650				$.ajax({
1651					url: meta.FETCH,
1652					data: data + "&listingCall=true",
1653					type: meta.METHOD,
1654					success: function(data, status, xhr) {
1655						if (fauxHead != null)
1656							$(fauxHead).remove();
1657						if (xhr.getResponseHeader("Content-Type") != 'text/javascript') {
1658							// We are updating the whole listing
1659							if (rowKey === undefined || rowKey == null) {
1660								$(div).replaceWith(data);
1661
1662							// We are only updating one row
1663							} else {
1664								// If we got data back just update the row's contents
1665								if (trim(data).length > 0)
1666									$("tr[data-row-key='" + rowKey + "']").html($(data).html());
1667								// Otherwise remove the row because the query is no longer returning it
1668								else
1669									$("tr[data-row-key='" + rowKey + "']").remove();
1670							}
1671						}
1672					}
1673				});
1674			}
1675
1676			if (!(afterRefresh === undefined) && afterRefresh != null)
1677				afterRefresh();
1678		};
1679
1680		// Store refreshView function on this element, so that we can refresh the listing from anywhere
1681		$(this).data("refreshView", refreshView);
1682
1683		// helper to bind pagination actions
1684		var bindPagination = function(e) {
1685
1686			var focusOnBody = function () {window.location.href="#body";}
1687
1688			// paging links
1689			$('ul.pagination a:not(.view-all)', e).click(function() {
1690				refreshView($(this).attr('rel'), focusOnBody);
1691				return false;
1692			});
1693
1694			// view all link
1695			$('ul.pagination a.view-all', e).click(function() {
1696				$("input.per-page",$(this).closest(".table-listing.dynamic")).val($(this).attr('rel'));
1697				refreshView(1, focusOnBody);
1698				return false;
1699			});
1700
1701			// paging selectbox
1702			$(':input.page-selector', e).change(function() {
1703				refreshView($(this).val(), focusOnBody);
1704				return false;
1705			});
1706
1707			// Bind Keys for < and > links
1708			$("ul.pagination", e).livequery(function() {
1709
1710				// Bind a key to the next item entry tab
1711				bindKey("ctrl+period", "Next Item Entry Tab", function(event, keys, combo) {
1712					var theUl = $("ul.pagination");
1713					if ($("ul.pagination").closest(".table-listing.primary-listing").length > 0) {
1714						theUl = $("ul.pagination", ".table-listing.primary-listing").eq(0);
1715					}
1716					$("li.arrow", theUl).eq(2).find("a").click();
1717				}, null);
1718
1719				// Bind a key to the previous item entry tab
1720				bindKey("ctrl+comma", "Previous Item Entry Tab", function(event, keys, combo) {
1721					var theUl = $("ul.pagination");
1722					if ($("ul.pagination").closest(".table-listing.primary-listing").length > 0) {
1723						theUl = $("ul.pagination", ".table-listing.primary-listing").eq(0);
1724					}
1725					$("li.arrow", theUl).eq(1).find("a").click();
1726				}, null);
1727			});
1728
1729			// paging selectbox
1730			$('input.per-page', e).change(function() {
1731				refreshView(1, focusOnBody);
1732			});
1733
1734			// numbered rows checkbox
1735			$('input.numbered-rows', e).change(function() {
1736				$(this).is(":checked") ? $(numberedCells).show() : $(numberedCells).hide();
1737				$("input.numbered-rows").not(this).attr("checked", $(this).attr("checked") || false);
1738				$(fauxHead).remove();
1739				fauxHead = null;
1740				$(window).scroll();
1741			});
1742
1743			return e;
1744		};
1745
1746		// sort links in table headers
1747		var bindSortingLinks = function(e) {
1748			$('a.sort', e).click(function() {
1749
1750				// create and append the loading mask
1751				appendLoadingMask(div);
1752
1753				// decide on sorting direction to apply
1754				var th = $(this).closest("th");
1755				var aMeta = $(th).metadata();
1756				var sortAsc = (aMeta.ASC != "NO");
1757				if ($(th).is(".sort-asc"))
1758					sortAsc = false;
1759				else if ($(th).is(".sort-desc"))
1760					sortAsc = true;
1761
1762				// create querystring with sort params
1763				var data = "sortColumn=" + aMeta.SORTNAME + "&sortBy=" + aMeta.SORTBY + "&sortAsc=" + sortAsc + "&perPage=" + parseInt($("input.per-page", div).val());
1764
1765				// append all filters
1766				if (filters) {
1767					var filterData = $(filters).serialize();
1768					if (filterData.length > 0)
1769						data += "&" + filterData;
1770				}
1771				if ($(":input", additionalFieldsForm).length > 0) {
1772					var extraData = $(additionalFieldsForm).serialize();
1773					if (extraData.length > 0)
1774						data += "&" + extraData;
1775				}
1776
1777				// Add _method=GET to the data if the _method key does not exist yet - this ensures Coldroute can still find the properly formed URLs via the GET route search table
1778				if (!/_method=/i.test(data))
1779					data += "&_method=GET";
1780
1781				// send ajax post
1782				$.ajax({
1783					url: meta.FETCH,
1784					data: data + "&listingCall=true",
1785					type: meta.METHOD,
1786					success: function(data) {
1787						if (fauxHead != null)
1788							$(fauxHead).remove();
1789						$(div).replaceWith(data);
1790					}
1791				});
1792				return false;
1793			});
1794
1795			return e;
1796		}
1797
1798		// bind click on links in multi-sort
1799		var bindMultiSortLinks = function(e) {
1800			$("a.remove", e).unbind("click").click(function() {
1801				if ($(e).siblings("li.sort").length == 0)
1802					$(e).siblings("li.placeholder").show()
1803				$(this).mouseout();
1804				$(e).remove();
1805				rebuildSort();
1806				return false;
1807			});
1808			$("a.flip", e).unbind("click").click(function() {
1809				var img = $("img", this);
1810				if ($(e).hasClass("sort-desc"))
1811					$(img).attr('src', img.attr('src').replace(/-desc\b/, "-asc"));
1812				else
1813					$(img).attr('src', img.attr('src').replace(/-asc\b/, "-desc"));
1814				$(e).toggleClass("sort-desc");
1815				rebuildSort();
1816				return false;
1817			});
1818			return e;
1819		};
1820
1821		// format query/filter spans when table loads
1822		formatQuery();
1823
1824		// show numbered cells if needed
1825		$("input.numbered-rows", this).is(":checked") ? $(numberedCells).show() : $(numberedCells).hide();
1826
1827		// bind click on existing multi-sort links
1828		$("li.sort", sortFields).each(function() {
1829			bindMultiSortLinks(this);
1830		});
1831
1832		// make sort fields area droppable
1833		if ($(element_sortQuery).length > 0) {
1834			$(sortFields).droppable({
1835				accept: "th.sortable",
1836				tolerance: "touch",
1837				activate: function(event, ui) {
1838					$(this).addClass("highlight-drop")
1839				},
1840				deactivate: function(event, ui) {
1841					$(this).removeClass("highlight-drop")
1842				},
1843				drop: function(event, ui) {
1844
1845					// grab sort metadata
1846					var meta = $(ui.draggable).metadata();
1847
1848					// only allow three sorts, and no repeat items, then append the new item
1849					if ($("li[rel*='" + meta.SORTNAME + "']", this).length > 0) {
1850						alert("You are already sorting by this item.");
1851					} else if ($("li.sort", this).length > 4) {
1852						alert("You may only sort by five items.");
1853					} else {
1854						var item = $("<li class='sort' />")
1855							.attr("rel", meta.SORTNAME)
1856							.addClass("{SORTNAME: '" + meta.SORTNAME + "',SORTBY: '" + meta.SORTBY + "'}");
1857						if ($(ui.draggable).hasClass("sort-desc"))
1858							$(item).addClass("sort-desc");
1859						$("<a href='#' class='remove' title='Click to remove' />")
1860							.html($("span.header-text", ui.draggable).html())
1861							.appendTo(item);
1862						$("<a href='#' class='flip' title='Click to flip sorting' />").append(
1863							$("img", ui.draggable)
1864								.clone()
1865								.attr('src', $("img", ui.draggable).attr('src').replace(/not-sorted/, "dark-asc"))
1866						).appendTo(item);
1867						bindMultiSortLinks(item);
1868						$("ul li.refresh", this).before(item);
1869						$("li.placeholder", this).hide();
1870						var label = $("> ul > li:eq(0)", this);
1871						if ($("span.important", label).length == 0)
1872							$(label).html($(label).html() + '<span class="important">*</span>');
1873						rebuildSort();
1874					}
1875				}
1876			});
1877
1878			// make sortable headers draggable
1879			$("th.sortable", head).draggable({
1880				helper: function() {
1881					var clone = $(this).clone()[0];
1882					$(clone).css("background-color", $(this).closest("tr").css("background-color"));
1883					return clone;
1884				},
1885				containment: div
1886			});
1887
1888			// make sort area refresh button work
1889			$("li.refresh a", sortFields).click(function() {
1890				refreshView(1);
1891				return false;
1892			});
1893		}
1894
1895		// Setup window scroll to execute its event stack
1896		$(window).scroll(function(event) {
1897			executeEventStack(this, "scroll", "after");
1898		});
1899
1900		// activate scrolling for thead
1901		if (!$(this).hasClass("no-floating-t-head")) {
1902			eventStackPush(window, "scroll", "after", "afterScroll:floatingThead");
1903			$(window).unbind("afterScroll:floatingThead").bind("afterScroll:floatingThead", function() {
1904				if ($(window).scrollTop() > headOffset.top) {
1905					if (fauxHead == null && $(head).is(':visible')) {
1906						var tableOffset = (navigator.appName == "Netscape") ? 2 : (navigator.appName == "Microsoft Internet Explorer") ? 1 : 0;
1907						fauxHead = $("<div id='faux-thead' class='table-listing'><table cellspacing='0' cellpadding='0' border='0' /></div>")
1908							.find("table").append($(head).clone()).css("width", $(div).find("> table").width() + tableOffset).end()
1909							.find("a").click(function() { return false; }).end()
1910							.width($(div).width())
1911							.css("font-size", $(div).css("font-size"))
1912							.prepend(bindPagination(
1913								$("div.pagination-fields", div).clone().css('background-color', $('> div.table-header', div).css('background-color'))
1914							))
1915							.appendTo(document.body);
1916						bindSortingLinks($("thead tr", fauxHead));
1917						$("th:visible", head).each(function() {
1918							$("th:eq(" + $(this).index() + ")", fauxHead).css("width", $(this).width() + 1);
1919						});
1920						if (floatWithFauxHead == null) {
1921							floatWithFauxHead = $(".float-with-floating-t-head").clone().addClass("float-with-floating-t-head-clone");
1922
1923							$(floatWithFauxHead).width($(div).width()).appendTo(document.body);
1924						}
1925						$(fauxHead).css({
1926							top: 0+$(floatWithFauxHead).outerHeight(),
1927							left: $(div).offset().left
1928						});
1929						$(floatWithFauxHead).css({
1930							top: 0,
1931							left: $(div).offset().left
1932						});
1933					}
1934
1935
1936				} else if(fauxHead != null) {
1937					$(fauxHead).remove();
1938					fauxHead = null;
1939					$(floatWithFauxHead).remove();
1940					floatWithFauxHead = null;
1941				}
1942				if (sidebarOffset && $(window).scrollTop() > sidebarOffset.top && $(sidebar).height() < $(window).height()) {
1943					$(sidebar).css("top", $(window).scrollTop());
1944				} else {
1945					$(sidebar).css("top", null);
1946				}
1947			});
1948		}
1949
1950		// Chrome doesn't like .scroll(); So, don't do it.
1951		if (!/Chrome[\/\s](\d+\.\d+)/.test(navigator.userAgent))
1952			$(window).scroll();
1953
1954		// enable sliders on range fields
1955		$("div.range-slider", "form.filter-inputs").livequery(function() {
1956
1957			// store some values for later
1958			var sliderDiv = this;
1959			var meta = $(sliderDiv).metadata();
1960			var rangeFields = $('#' + $(sliderDiv).attr('id') + ' ~ input');
1961			var rangeValues = eval(meta.RANGEVALUES);
1962
1963			// save the values of the range fields so we can conditionally include them in the filters
1964			$(rangeFields).each(function () {$(this).data("loadVal", $(this).val());});
1965
1966			// define the function that will update the spans so the user sees the change
1967			var updateRangeDisplay = function(from,to) {
1968				var spans = $(sliderDiv).next("span.range-values").find("span");
1969				$(spans[0]).html($.formatNumber(from, {format:eval('numberFormats.' + meta.RANGEFORMAT), locale:"us"}));
1970				$(spans[1]).html($.formatNumber(to, {format:eval('numberFormats.' + meta.RANGEFORMAT), locale:"us"}));
1971			}
1972
1973			// call the update display function once to format the numbers initially
1974			updateRangeDisplay(rangeValues[0], rangeValues[1]);
1975
1976			// create the slider object
1977			$(sliderDiv).slider({
1978				range: true,
1979				min: meta.RANGEMIN,
1980				max: meta.RANGEMAX,
1981				step: (meta.RANGEMAX - meta.RANGEMIN) / meta.RANGESTEPS,
1982				values: rangeValues,
1983				slide: function(event, ui) {
1984					$(rangeFields[0]).val(ui.values[0]);
1985					$(rangeFields[1]).val(ui.values[1]);
1986					updateRangeDisplay(ui.values[0], ui.values[1]);
1987				}
1988			});
1989		});
1990
1991		// add filter form
1992		$("form.filter-inputs", this).submit(function() {
1993
1994			// remove range filters if they haven't been changed (prevents undesired filters)
1995			$("div.range-slider", this).each(function() {
1996				// get the inputs under this slider, if any of them have their default values we're not changing them
1997				$('#' + $(this).attr('id') + ' ~ input').each(function() {
1998					if ($(this).val() == $(this).data("loadVal"))
1999						$(this).remove();
2000				});
2001			});
2002
2003			// grab new filter input values
2004			$(":input:not(:button,:submit)", this).each(function() {
2005				var tagType = $(this).prop("type");
2006				if ($(this).val().length > 0 && $(this).val() != "EMPTY" && (tagType.toLowerCase() != 'checkbox' || $(this).is(':checked'))) {
2007					var meta = $(this).metadata();
2008					var name = $(this).attr("name").replace(/searchItem\[([^\]]+)\]/, "$1");
2009					var value = $(this).val();
2010					var query = $(queryField).val();
2011					var exp = "";
2012
2013					// simple function to add quotes to list
2014					var quote_value = function(str, list) {
2015						if (meta.TYPE != "string" && meta.TYPE != "char")
2016							return str;
2017						if (list === undefined || list == true)
2018							str = str.replace(/,/g, "','");
2019						return "'" + str + "'";
2020					};
2021
2022					// escape operation (and consider IN operations if stackable)
2023					var op = meta.OPERATION.replace(/(\*|\$|\^)/, "\\$1");
2024					if (meta.STACKABLE)
2025						op = "(?:IN|" + op + ")";
2026
2027					// use regex to decide if value is already set
2028					var regex = new RegExp(name + "\\s+" + op + "\\s+\\(?((?:'[^']+'|\\d+)\\.?(?:,? *(?:'[^']+'|\\d+))*|EMPTY)\\)?");
2029					var existing = regex.exec(query);
2030
2031					// build appropriate comparison string
2032					if (meta.STACKABLE) {
2033
2034						// die if value is not a valid list
2035						if (/^[^,]+(,[^,]+)*$/.test(value) == false)
2036							return false;
2037
2038						// if existing values exist
2039						if (existing != null) {
2040
2041							// replace existing values
2042							var existingValue = existing[1].replace(/'/g, "");
2043							var existingArray = existingValue.split(",");
2044							for (var i = 0; i < existingArray.length; i++)
2045								value = listDelete(value, existingArray[i]);
2046
2047							// append new values to existing
2048							if (existingValue.length > 0 && value.length > 0)
2049								value = existingValue + "," + value;
2050							else if (existingValue.length > 0)
2051								value = existingValue;
2052						}
2053
2054						// create expression
2055						if (/,/.test(value))
2056							exp = name + " IN (" + quote_value(value) + ")";
2057						else
2058							exp = name + " " + meta.OPERATION + " " + quote_value(value, false);
2059
2060					} else {
2061						exp = name + " " + meta.OPERATION + " " + quote_value(value, false);
2062					}
2063
2064					// prevent duplicates
2065					if (existing != null) {
2066
2067						// replace to prevent duplicate operations
2068						query = query.replace(regex, exp);
2069
2070					} else {
2071
2072						// add value to query
2073						if (query.length > 0)
2074							query += " && ";
2075						query += exp;
2076					}
2077
2078					// update and show the query in the bar
2079					$(queryField).val(query);
2080					formatQuery();
2081				}
2082			});
2083			refreshView(1);
2084			return false;
2085		});
2086
2087		// close button on filter form
2088		$("form.filter-inputs :input[name='closeFilters']", this).click(function() {
2089			$(this.form).hide();
2090			return false;
2091		});
2092
2093		// add filter link
2094		$('a.add-filter', this).click(function() {
2095			var div2 = $('form.filter-inputs', div);
2096			$(div2).toggle().find(":input:visible:first").focus();
2097			return false;
2098		});
2099
2100		// call add/remove from listing select
2101		$(":input.input-checkbox.listing-select:not(.toggle-checkboxes-in-column)").livequery(function() {
2102
2103			$(this).unbind("change");
2104
2105			$(this).change(function() {
2106				var listingName = $(this).closest("div.table-listing").attr("id").replace(/^table-listing-/, '').replace(/-(\w)/g, function(str, match) {return match.toUpperCase();});
2107				var columnName = $(this).data("column-name");
2108				var controller = $(this).closest("div.table-listing").data("controller");
2109				// var url = $(this).closest("div.table-listing").metadata().FETCH;
2110				// url = url.substring(url.lastIndexOf("/")).toString().replace(/,/g, "/");
2111				var parent = $(this).parent();
2112
2113				appendLoadingMask($(parent));
2114				if ($(this).is(":checked")) {
2115					$.ajax({
2116						url: "/" + controller + "/add-to-listing-select?listingName=" + listingName + "&columnName=" + columnName + "&listingSelectData=" + $(this).val(),
2117						method: "get",
2118						success: function() {$destroy($(parent).find(".loading-mask"));}
2119					});
2120				} else {
2121					$.ajax({
2122						url: "/" + controller + "/remove-from-listing-select?listingName=" + listingName + "&columnName=" + columnName + "&listingSelectData=" + $(this).val(),
2123						method: "get",
2124						success: function() {$destroy($(parent).find(".loading-mask"));}
2125					});
2126				}
2127
2128			});
2129		});
2130
2131		// on click, do bulk processing using check box values
2132		$(bulkActions).click(function(event) {
2133			var href = $(this).attr("href"),
2134				colIndex = $("> table > thead > tr > th:has(:checkbox)", div).index(),
2135				dataElements = $("> table > tbody > tr > td:nth-child(" + (colIndex + 1) + ") > input:checkbox:checked", div),
2136				method = $(this).data("method"),
2137				remote = $(this).data("remote"),
2138				confirmMessage = $(this).data("confirm") || "Are you sure you want to perform this action on the selected records?";
2139
2140			// Can't do anything with no data.
2141			if ($(dataElements).length == 0) {
2142				alert("You must select at least one row to perform this action");
2143				return stopEvent(event);
2144
2145			// Do it!
2146			} else {
2147				// Only confirm if we aren't already using a facebox to confirm the bulk action
2148				if ($(this).hasClass("facebox") || confirm(confirmMessage)) {
2149					// Handle the data attribute manually (modeled after jQuery-rails script), so we can do bulk actions with posts and such
2150					if (method != null) {
2151						var form = $('<form method="post" action="' + href + '"></form>');
2152						if (remote != null)
2153							$(form).attr("data-remote", remote);
2154						$(form).hide().append('<input type="hidden" name="_method" value="' + method + '" />').appendTo("body");
2155						$(dataElements).appendTo(form);
2156						appendLoadingMask($(bulkActions).closest("div"));
2157						$(form).bind("ajax:complete", function() {$(bulkActions).closest("div").find("div.loading-mask").remove();});
2158						$(form).submit();
2159					} else {
2160						if ($(this).hasClass("facebox"))
2161							$.facebox({ajax: href + (href.indexOf('?') != -1 ? '&' : '?') + $(dataElements).serialize()});
2162						else
2163							window.location = href + (href.indexOf('?') != -1 ? '&' : '?') + $(dataElements).serialize();
2164					}
2165				}
2166				return stopEvent(event);
2167			}
2168		});
2169
2170		// on submit for additional fields and sidebar forms
2171		$(additionalFieldsForm).add(sidebarForm).unbind("submit.listing").bind("submit.listing", function() {
2172			refreshView(1);
2173			return false;
2174		});
2175
2176		// allow sidebar content to be cleared
2177		$(":button.clear-sidebar", sidebarForm).unbind("click").click(function() {
2178			$("input[type='text'],textarea", sidebarForm).val("");
2179			$("select", sidebarForm).val("EMPTY").change();
2180			$("div#sidebar.expanded").click();
2181			$(sidebarForm).submit();
2182		});
2183
2184		// on change for extra fields
2185		$(additionalFields)
2186			.filter(':checkbox,select').unbind("change.listingField").bind("change.listingField", function() {
2187				$(this.form).submit();
2188			}).end()
2189			.filter(':button').unbind("click.listingField").bind("click.listingField", function() {
2190				$(this.form).submit();
2191			}).end()
2192			.not(':checkbox,:button,select').unbind("keydown.listingField").bind("keydown.listingField", function(e) {
2193				var k = e.keyCode ? e.keyCode : e.which;
2194				if (k == 13) $(this.form).submit();
2195			});
2196
2197		// make sidebar operational
2198		if ($(sidebarForm).closest("div#body").length == 1) {
2199			$("body").addClass("has-sidebar");
2200			var sidebarFormClick = false;
2201			$("<div />")
2202				.attr("id", "sidebar")
2203				.append($(sidebarForm).show()) // The sidebar starts out as hidden so it does not impact page loading performance
2204				.insertAfter("div#header")
2205				.click(function(e) {
2206					if (sidebarFormClick == false) {
2207						if ($(this).hasClass("expanded"))
2208							$(this).removeClass("expanded").css("left", $(this).width() * -1).next().css("margin-left", "16px");
2209						else
2210							$(this).addClass("expanded").css("left", 0).next().css("margin-left", $(this).width() + 16);
2211					}
2212					sidebarFormClick = false;
2213				})
2214				.each(function() {
2215					$(this).css("width", $(this).width() + 20);
2216					$(this).css({
2217						left: $(this).width() * -1,
2218						backgroundPosition: ($(this).width() + 3) + "px center"
2219					});
2220					if ($(sidebarForm).hasClass("expanded"))
2221						$(this).click();
2222				})
2223				.find("form").click(function(e) {
2224					sidebarFormClick = true;
2225				});
2226		}
2227		sidebar = $("div#sidebar");
2228		sidebarOffset = sidebar.offset();
2229
2230		// links to [x] out an existing filter
2231		$('a.remove-filter', this).click(function() {
2232			$(this).closest("li").remove();
2233			refreshView(1);
2234			return false;
2235		});
2236
2237		// bind pagination to this div
2238		bindPagination(this);
2239
2240		// bind the sorting links to this div
2241		bindSortingLinks(this);
2242	});
2243
2244	// Truly disable disabled buttons in listing facebox if it is disabled.
2245	$("div.user-default-options a.disabled").livequery(function() {
2246		$(this).click(function(event) {
2247			event.preventDefault();
2248		});
2249	});
2250
2251	// enabled highlighting of rows on hover and click
2252	$("div.table-listing.highlight > table > tbody > tr:not(.edit-form-row)").livequery(function() {
2253		$(this).click(function() {
2254			$(this).hasClass("highlight") ? $(this).removeClass("highlight") : $(this).addClass("highlight");
2255		});
2256		$(this).hover(function() {
2257			$(this).addClass("hover");
2258		}, function() {
2259			$(this).removeClass("hover");
2260		});
2261	});
2262
2263	// enabled highlighting of rows on focus of input field
2264	$("div.table-listing.highlight-on-input-focus > table > tbody > tr:not(.edit-form-row) > td > :input").livequery(function() {
2265		$(this).focusin(function() {
2266			$(this).closest("tr").removeClass("highlight-on-input-focus").addClass("highlight-on-input-focus");
2267		});
2268		$(this).focusout(function() {
2269			$(this).closest("tr").removeClass("highlight-on-input-focus");
2270		});
2271	});
2272
2273	// if a table has a column that contains "collapsable" text we want to enforce column widths to prevent jumpy listings
2274	$("div.table-listing table").livequery(function() {
2275		if ($("span[class*='toggle-text-']", this).length > 0) {
2276
2277			// loop the th cells and set their css width to their current width
2278			$("th:visible", this).each(function() {
2279				$(this).css("width", $(this).width() + 'px');
2280			});
2281		}
2282	});
2283
2284	// If a table has aggregates we want to prepare our data and send it all up to CF
2285	$("div.table-listing.aggregated-listing").livequery(function() {
2286		var aggregates = $("div[data-aggregate-column]", this),
2287			columnData = {columns : {}};
2288
2289		// Build the list of columns and types for CF
2290		$(aggregates).each(function() {
2291			columnData.columns[$(this).data("aggregate-column")] = {
2292				type: $(this).data("aggregate-type"),
2293				format: $(this).data("aggregate-format") != null ? $(this).data("aggregate-format") : ''
2294			};
2295			appendLoadingMask(this);
2296		});
2297
2298		// Prepare the listing name and url for sending the request
2299		var listingName = $(this).attr("id").replace(/^table-listing-/, '').replace(/-(\w)/g, function(str, match) {
2300				return match.toUpperCase();
2301			}),
2302			url = $(this).metadata().FETCH.split("/");
2303
2304		// Regex failed above, so we used a split method. This finishes cleaning up the url for maintaining the controller.
2305		url.pop();
2306		url = url.toString().replace(/,/g, "/");
2307
2308		// Send the data up to CF
2309		$.get("/" + $(this).data("controller") + "/get-aggregates-for-listing?listingName=" + listingName, columnData, function(data) {
2310			// Set the return value in the column headers based on column name
2311			$(aggregates).each(function() {
2312				var currColumnName = $(this).data("aggregate-column"),
2313					currTypes = $(this).data("aggregate-type").split(","),
2314					iEnd = currTypes.length;
2315
2316				// Remove the loading mask
2317				$destroy($(this).find(".loading-mask"));
2318
2319				// If this column has data we need to append it to the div
2320				for (var i=0; i < iEnd; i++) {
2321
2322					// Loop each type of aggregate requested (sum,avg,min,max,etc) and create a span for each value returned
2323					var currType = currTypes[i];
2324					if (data[currColumnName][currType] !== undefined) {
2325						// Create a span to hold the new value
2326						var newSpan = $('<span class="help-bubble" title="Aggregate Type: ' + currType.toUpperCase() + '" />').html(data[currColumnName][currType]);
2327						$(this).append(newSpan);
2328					}
2329				}
2330			});
2331		}, "json");
2332	});
2333
2334	// Set this on the trigger element inside the row
2335	$('.row-edit-opener,.facebox-opener').livequery(function() {
2336
2337		// Pass a guid as a listing name suffix to ensure unique session params for any listings within the drill-down
2338		$(this).attr("rel", $(this).attr("rel").replace(/\?/, "?listingNameSuffix=" + generateGUID().replace(/-/g, "") + "&"));
2339
2340		$(this).click(function(event) {
2341
2342			// Add the row-is-open class. row-is-open class should be removed before the edit-form-row is removed.
2343			$(this).removeClass("row-is-open").addClass("row-is-open");
2344
2345			// function to bind form actions to facebox or inline form
2346			var bindFormActions = function (e, editorRow) {
2347
2348				// will only exist when we're coming from a row. Otherwise it will be a facebox
2349				var editor = (editorRow === undefined) ? $('div#facebox') : editorRow;
2350
2351				$('form:not(.ajax-form)', editor).attr("data-remote", true).bind("ajax:success", function(event, data, status, xhr) {
2352					if (event.target.tagName == 'FORM') {
2353						data = trim(data);
2354						if (data.length > 0 && (xhr.getAllResponseHeaders() || '').indexOf("text/javascript") == -1) {
2355							if (!$(data).is("tr")) {
2356								$(this).replaceWith(data);
2357								bindFormActions(e, editorRow);
2358							} else {
2359								updateRowAfterRowEditForm(data, editor);
2360
2361								//Now close the form container
2362								if(editorRow === undefined) {
2363									$(document).trigger('close.facebox');
2364								} else {
2365									$($(editorRow).data('opener')).removeClass("editing-row");
2366									$("a.row-edit-opener.row-is-open", $(editorRow).prev("tr")).removeClass("row-is-open");
2367									$(editorRow).remove();
2368								}
2369							}
2370						}
2371					}
2372				});
2373
2374				$('.cancel', editor).click(function() {
2375					if(editorRow === undefined) {
2376						$(document).trigger('close.facebox');
2377					} else {
2378						$($(editorRow).data('opener')).removeClass("editing-row");
2379						$("a.row-edit-opener.row-is-open", $(editorRow).prev("tr")).removeClass("row-is-open");
2380						$(editorRow).remove();
2381					}
2382				});
2383			};
2384
2385			// function to update form fields in response to json data
2386			var updateRowAfterRowEditForm = function (data, formContainer) {
2387
2388				// Retrieve the row we modified
2389				var theRow = $(formContainer).data('opener');
2390
2391				// get the classes
2392				var classList = $(theRow).attr('class');
2393
2394				// replace the row and add old classes
2395				theRow = $(data).replaceAll(theRow).attr('class', classList);
2396
2397				// put new variable back in opener storage
2398				$(formContainer).data('opener', theRow);
2399			};
2400
2401			// store the current TR element
2402			var theTR = $(this).closest('tr'),
2403				theTable = $(this).closest("table");
2404
2405			// Store the current width of this table in its data
2406			if ($(theTable).data("currentWidth") == null)
2407				$(theTable).data("currentWidth", $(theTable).width());
2408
2409			// close inline edit form if it is open
2410			if ($(theTR).next("tr.edit-form-row").length > 0) {
2411				$(theTR).removeClass("editing-row");
2412				$("a.row-edit-opener.row-is-open", $(theTR)).removeClass("row-is-open");
2413				$(theTR).next("tr.edit-form-row").remove();
2414				return false;
2415			}
2416
2417			// We're loading via facebox
2418			if($(this).hasClass('facebox-opener')) {
2419
2420				// bind into facebox reveal event
2421				$(document).bind('afterReveal.facebox', bindFormActions);
2422
2423				// perform asynchronous facebox load
2424				$.ajaxSetup({async: false});
2425				$.facebox({ajax: $(this).attr('rel')});
2426				$.ajaxSetup({async: true});
2427
2428				// store the TR for the facebox form to use
2429				$('div#facebox').data('opener', theTR);
2430
2431			// We're loading via a row
2432			} else {
2433
2434				// Find the row we're editing
2435				var editRowNumber = ($("tr[id*='edit-form-row']").length + 1);
2436
2437				// Add a new row after it with a unique id
2438				var editorRow = $("<tr id='edit-form-row-" + editRowNumber + "' class='edit-form-row " + $(theTR).attr('class') + "'><td colspan='" + $('td', theTR).length + "'></td></tr>")
2439					.removeClass("hover").removeClass("highlight")
2440					.insertAfter($(theTR).addClass("editing-row"));
2441
2442				// Load into the new row storing the TR that opened this one so we can reopen it when necessary
2443				$(editorRow).data('opener', theTR).find('td').load($(this).attr('rel'), '',  function() {
2444					bindFormActions(event, editorRow);
2445					$(editorRow).show(0, function() {
2446						var newTableWidth = $(theTable).width();
2447						if (newTableWidth > $(theTable).data("currentWidth")) {
2448							$(theTable).css("width", newTableWidth);
2449							$(theTable).data("currentWidth", newTableWidth);
2450						}
2451					});
2452				}).css('padding', '1em');
2453			}
2454
2455			return false;
2456		});
2457	});
2458
2459	$("div.ui-association").livequery(function() {
2460		$(this).association();
2461	});
2462
2463	$("div.ui-association").find('input').livequery(function() {
2464		$(this).keyup(function() {
2465			if ($(this).attr('name').indexOf('[undefined]') >= 0 && $(this).val() != "")
2466				$(this).change();
2467		});
2468	});
2469
2470	$("div.edit-section > h3 > span.link.collapse a").livequery(function() {
2471		// store handlers for faster access
2472		var div = $(this).closest("div.edit-section");
2473		var content = $("> div.content", div);
2474		var image = $("img", this);
2475		var theLink = this;
2476
2477		$(this).click(function(event) {
2478			if ($(div).hasClass("collapsed")) {
2479				// hide other contents
2480				$("div.edit-section").not(".collapsed").find("h3 > span.link.collapse a").each(function() {
2481					$(this).click();
2482				});
2483				// expand the contents
2484				$(image).attr("src", "/tpWheels/images/icons/collapse.png");
2485				$(theLink).attr("title","Collapse");
2486				$(div).removeClass("collapsed");
2487				$(content).slideDown();
2488			} else {
2489				// hide the contents
2490				$(content).slideUp(function() {
2491					$(image).attr("src", "/tpWheels/images/icons/expand.png");
2492					$(theLink).attr("title","Expand");
2493					$(div).addClass("collapsed");
2494				});
2495			}
2496			event.preventDefault();
2497		});
2498		// start all steps in collapsed mode except for the first one by default, unless there's only one
2499		if ($("div.edit-section > h3 > span.link.collapse a").length == 1)
2500			$(this).click();
2501	});
2502
2503	$(":input.numeric-input-only").livequery(function() {
2504		// Blocks most invalid keystrokes from getting into numerc only fields.
2505		// [ ] { } " - These strokes are not prevent due to complications with regex formatting. However, to input one of these, you have to try hard.
2506		// _ and % are not included, and should not be. They are handy for regexing in SQL.
2507		var invalidKeys = new RegExp("[a-zA-Z,./;'<>?:]");
2508		$(this).keypress(function(event) {
2509			var key = String.fromCharCode(event.which ? event.which : event.keyCode);
2510			if (invalidKeys.test(key))
2511				return stopEvent(event);
2512		});
2513	});
2514
2515	// Append spinner to ajax forms and ajax links which have spinner class
2516	$(document).on("ajax:before", "form[data-remote]:not(.no-spinner),a[data-remote].spinner", function() {
2517		appendLoadingMask(this);
2518	}).on("ajax:complete ajax:error", "form[data-remote]:not(.no-spinner),a[data-remote].spinner", function() {
2519		$destroy($(this).find("> div.loading-mask"));
2520	});
2521
2522	// Stop requested events for elements that specify them
2523	$("*[data-prevent-bubbling]").livequery(function() {
2524		var eventsToStop = $(this).attr("data-prevent-bubbling").split(",");
2525		for (var key in eventsToStop)
2526			$(this).bind(eventsToStop[key], function(event) {return event.stopPropagation();});
2527	});
2528
2529	$(":input.spinner").livequery(function() {
2530		$(this).click(function() {
2531			appendLoadingMask($(this).parent());
2532		});
2533	});
2534
2535	// Add loading mask for non-ajax spinner links; Remove it when user clicks cancel on a confirm link
2536	$("a.spinner:not([data-remote])").livequery(function() {
2537		$(this).click(function() {
2538			appendLoadingMask($(this).parent());
2539		});
2540	}).on("confirm:complete", function(e,answer) {
2541		if (!answer) $destroy($(this).parent().find("> div.loading-mask"));
2542	});
2543
2544	$("div#column-selection-prompt").livequery(function() {
2545		var element_columnSelectionPrompt = this;
2546		// Select/Deselect all columns
2547		$('#field-toggle-select-all-0', element_columnSelectionPrompt).change(function() {
2548			if (!$(this).is(":checked"))
2549				$('input[name="selectedColumns"]', element_columnSelectionPrompt).attr('checked', false);
2550			else
2551				$('input[name="selectedColumns"]', element_columnSelectionPrompt).attr('checked', true);
2552		});
2553	});
2554
2555	// Before exporting attempt to pass up the filters for exporting to html / pdf
2556	$("div#export-prompt").livequery(function() {
2557		var theExportPrompt = this,
2558			theForm = $(this).find("form"),
2559			cookieId = "export-download-" + generateGUID();
2560
2561		// Mark this export's cookie as preparing and add it to the form for the server to use
2562		setCookie(cookieId, "preparing", 1);
2563		$(theForm).append($('<input type="hidden" name="cookieId" value="' + co
2563okieId + '"/>"'));
2564		$(this).attr("cookieId", cookieId);
2565
2566		// Default the print title if we have an h2 on the page
2567		if ($(document.body).find("h2").length > 0) {
2568			var printTitle = $(document.body).find("h2:first").clone().find("img,a").remove().end().text();
2569			$("input#field-print-title", this).val(printTitle);
2570		}
2571
2572		// If the user chooses html or pdf, display the printer controls (title & print filters checkbox)
2573		$(":input[name='exportFormat']", this).change(function() {
2574			var thisVal = $(this).val();
2575			if (thisVal == "html" || thisVal == "pdf")
2576				$("ul#export-print-controls").removeClass("hidden");
2577			else
2578				$("ul#export-print-controls").removeClass("hidden").addClass("hidden");
2579		});
2580
2581		// Append the additional-fields form data from the listing to the facebox's form
2582		var filters = [];
2583		$(":input:visible:not(:button,:submit)","form.additional-fields").each(function() {
2584			var thisVal = $(this).val();
2585
2586			// Include only non-empty inputs
2587			if (!(thisVal == "" || thisVal == 'EMPTY' || ($(this).is(":checkbox") && !$(this).is(":checked")))) {
2588				var parent = $(this).parent(),
2589					label = $(parent).is("label") ? parent : $(parent).find("label[for='" + $(this).attr("id") +"']");
2590
2591				// If we didn't find a label be lazy about it. Grab the first visible one under the parent if possible.
2592				if (label == null || $(label).length == 0)
2593					label = $(parent).find("label:visible").first();
2594
2595				// Remove unwanted content from the label
2596				label = $(label).find("img,a").remove().end().html();
2597
2598				// Match the value against the label
2599				filters[filters.length] = {
2600					label: label,
2601					value: $(this).is("select") ? $(this).find("option:selected").html() : $(this).val()
2602				};
2603			}
2604		});
2605
2606		// Append the filter values as a hidden input on the form
2607		var filterInput = $('<input type="text" name="additionalFieldsData" id="additional-fields-data" />').hide();
2608		$(filterInput).val(JSON.stringify(filters)).appendTo($(this).find("form"));
2609
2610		// Select/Deselect all columns
2611		$('#field-toggle-select-all-0', theExportPrompt).change(function() {
2612			if (!$(this).is(":checked"))
2613				$('input[name="exportColumns"]').attr('checked', false);
2614			else
2615				$('input[name="exportColumns"]').attr('checked', true);
2616		});
2617
2618		$(theForm).submit(function() {
2619			setCookie(cookieId, "running", 1);
2620			var intervalHandle = setInterval(function() {
2621				if (getCookie(cookieId) == "complete") {
2622					$(document).trigger('close.facebox');
2623					deleteCookie(cookieId);
2624					$(theExportPrompt).attr("cookieId", "");
2625					clearInterval(intervalHandle);
2626				}
2627			}, 1000);
2628		});
2629	}, function() {
2630		if ($(this).attr("cookieId") !== undefined && $(this).attr("cookieId") != "")
2631			deleteCookie($(this).attr("cookieId"));
2632	});
2633
2634	// Make force-no-wrap elements auto-size to the necessary width to accomodate the direct elements underneath it
2635	$(".force-no-wrap").livequery(function() {
2636		var container = this,
2637			directElements = $(this).find("> *:visible"),
2638			resizeTo = 0;
2639
2640		// Sum up the outerWidth (includes padding, margin, and borders) for the direct descendants
2641		$(directElements).each(function() {
2642			if (/\b(x-small|small|medium|large|x-large|full)\b/.test($(this).attr("class")))
2643				$(this).width(parseInt($(this).width()));
2644			resizeTo += $(this).outerWidth(true);
2645		});
2646
2647		// Fix the size.
2648		$(container).width(resizeTo);
2649	});
2650
2651	// Enable CKEditors on textareas (Ensure CKEDITOR.config.replaceClass is set to '' in ckeditor/config.js. replace can be called manually to allow for specific options per instance.)
2652	$("textarea.ckeditor").livequery(function() {
2653		var config = {};
2654		if ($(this).attr("data-ckeditor"))
2655			$.extend(config, $.parseJSON(urldecode($(this).attr("data-ckeditor"))));
2656		else if ($(this).data("ckeditor"))
2657			$.extend(config, $.parseJSON(urldecode($(this).data("ckeditor"))));
2658		if ($(this).attr("data-width"))
2659			$.extend(config, {width: $(this).attr("data-width")});
2660		CKEDITOR.replace(this, config);
2661	});
2662
2663	// Enable refresh every
2664	$("select#refresh-window-after-x-seconds").livequery(function() {
2665
2666		// I'm going on break
2667		var refresher = this;
2668		var refresherImage = $("img#refresh-window-after-image");
2669
2670		// Function that will submit the form and refresh the page
2671		var refresh = function() {
2672			$($(refresher)[0].form).submit();
2673		}
2674
2675		// Default the timer
2676		if ($(refresher).val() == 0)
2677			$(refresher).data("timer", null);
2678		else
2679			$(refresher).data("timer", setInterval(refresh, $(refresher).val() * 1000));
2680
2681		// Setup the click on the image
2682		$(refresherImage).css("cursor", "pointer").click(function(event) {refresh(); return stopEvent(event);});
2683
2684		// On change reset the timer
2685		$(refresher).change(function(event) {
2686
2687			// Clear out the interval that's currently there
2688			if ($(refresher).data("timer") != null) {
2689				clearInterval($(refresher).data("timer"));
2690				$(refresher).data("timer", null);
2691			}
2692
2693			// Setup the new interval
2694			if ($(refresher).val() > 0)
2695				$(refresher).data("timer", setInterval(refresh, $(refresher).val() * 1000));
2696
2697			return stopEvent(event);
2698		});
2699	});
2700
2701	// allow remote form data to come from other sources than just the from (such as ui:listings)
2702	$("form[data-remote][data-include-fields-from]").livequery("ajax:beforeSend", function(event, xhr, settings) {
2703		settings.data += "&" + $(":input", $(this).data("include-fields-from")).filter(function() {
2704			return this.form == null && !$(this).is(":checkbox:not(:checked)");
2705		}).serialize();
2706	});
2707
2708	// allow very general polling behavior; expects javascript partial to be returned over ajax
2709	$("[data-poll]").livequery(function() {
2710		var poll_element = this,
2711			poll_url = $(this).attr("data-poll"),
2712			poll_time = $(this).attr("data-poll-interval") != null ? $(this).attr("data-poll-interval") : 5000,
2713			poll_interval = null,
2714			poll_last_runat = null,
2715			poll_on_load = $(this).attr("data-poll-on-load") != null ? $(this).attr("data-poll-on-load") == 'true' : true,
2716
2717			pollIntervalFunction = function() {
2718				// Only let the poll run if this is the active tab
2719				if (window.name == getCookie("activeTab")) {
2720					$.get(poll_url, {poll: true}, function(data) {
2721						if (data.length > 0)
2722							eval(data);
2723					}, 'text');
2724					poll_last_runat = new Date();
2725				}
2726			},
2727
2728			startPolling = function() {
2729				stopPolling();
2730				poll_interval = setInterval(pollIntervalFunction, poll_time);
2731				$(poll_element).attr("data-poll-interval-id", poll_interval);
2732
2733				/* Handle when the element loads for the first time and when the user is switching between tabs.
2734					If this is the element's creation and we want to poll on load, run the polling function.
2735					If this is not the element's creation but the startPolling function is called for this element (the user switched back to the tab) \
2736						check if the tab was idle long enough for at least one run to have occured. If so, run the polling function. */
2737				if ((poll_last_runat == null && poll_on_load) || (poll_last_runat != null && new Date() - poll_last_runat >= poll_time))
2738					pollIntervalFunction();
2739			},
2740
2741			stopPolling = function() {
2742				if (poll_interval != null) {
2743					clearInterval(poll_interval);
2744					poll_interval = null;
2745					$(poll_element).attr("data-poll-interval-id", poll_interval);
2746				}
2747			};
2748
2749		// Bind the functions to this element's data so the window blur/focus can use them
2750		$(this).data("stopPolling", stopPolling).data("startPolling", startPolling);
2751
2752		// Kick off the polling no matter what this element loads. The pollIntervalFunction will handle if it is the active tab or not.
2753		startPolling();
2754	}, function() {
2755		if ($(this).attr("data-poll-interval-id") != null)
2756			clearInterval($(this).attr("data-poll-interval-id"));
2757	});
2758
2759	// Automatically post on enter
2760	$("form.submit-on-enter :input").livequery("keypress", function(event) {
2761		var key = event.keyCode ? event.keyCode : event.which;
2762		if (key == KEYENUM.RETURN) {
2763			$(this.form).submit();
2764			return stopEvent(event);
2765		}
2766	});
2767
2768	// Bind elements to toggle the display of a specified element based on a value
2769	$(":input[data-toggle-show]").livequery(function() {
2770		var matcher = $(this).attr("data-toggle-show");
2771		var exactVal = $(this).attr("data-toggle-value");
2772		var emptyVal = $(this).is("select") ? "EMPTY" : "";
2773		$(this).change(function() {
2774			var val = $(this).val();
2775			if ($(this).is(":checkbox:checked") || exactVal != null && val == exactVal || exactVal == null && val != emptyVal)
2776				$(matcher).show();
2777			else
2778				$(matcher).hide();
2779		}).change();
2780	});
2781
2782	// Do the oposite of the above function.
2783	$(":input[data-toggle-hide]").livequery(function() {
2784		var matcher = $(this).attr("data-toggle-hide");
2785		var exactVal = $(this).attr("data-toggle-value");
2786		var emptyVal = $(this).is("select") ? "EMPTY" : "";
2787		$(this).change(function() {
2788			var val = $(this).val();
2789			if ($(this).is(":checkbox:checked") || exactVal != null && val == exactVal || exactVal == null && val != emptyVal)
2790				$(matcher).hide();
2791			else
2792				$(matcher).show();
2793		}).change();
2794	});
2795
2796	// allow checkboxes to act like radio buttons
2797	$("input[type='checkbox'].radio").on("change", function(e) {
2798		if ($(this).is(':checked'))
2799			$("input[type='checkbox'][name='" + $(this).attr("name") + "'].radio").not(this).attr("checked", false).change();
2800	});
2801
2802	// enable a red X to reset certain field values
2803	$("form.additional-fields :input:visible:not(:checkbox, :radio), form.additional-fields :input.autocomplete, :input:visible.clearable").livequery(function() {
2804		var $input = $(this),
2805			value = $input.is("select") ? $input.find("option:first-child").attr("value") : '',
2806			$label = $(this).closest("label").add("label[for='" + $(this).attr("id") + "']");
2807		if ($label.length == 1) {
2808			var link = $("<a href='#' class='clear-field' tabindex='-1'>[x]</a>").click(function(e) {
2809				$input.not(":disabled").val(value).change().focus();
2810				if ($input.is(".autocomplete")) {
2811					$label.next("input.ui-autocomplete-input", $(this)).val("");
2812					$input.next("input.autocomplete-hidden").val('').change();
2813				}
2814				return stopEvent(e);
2815			});
2816			$label.append(link.hide());
2817			var show_or_hide = function(e) {
2818				$input.val() == value ? link.hide() : link.show();
2819			};
2820			show_or_hide();
2821			$input.bind("change.clearable", show_or_hide);
2822			$input.bind("change.autocomplete", show_or_hide);
2823		}
2824	});
2825
2826	// do the Christopher Lambert thing
2827	$(".there-can-be-only-one :input").livequery('change', function() {
2828		$(this).closest(".there-can-be-only-one").find(":input").not(this).not(".there-can-be-only-one-exception").val('');
2829	});
2830
2831	// do the Christopher Lambert thing again..again
2832	// necessary for all inputs to be affected to have the data attribute on them
2833	$(":input[data-there-can-be-only-one-within]").livequery('change', function() {
2834		var theDataValue = $(this).attr("data-there-can-be-only-one-within");
2835		$(this).closest(theDataValue).find(":input[data-there-can-be-only-one-within]").not(this).not(":input[type='checkbox']").filter(function() {
2836			return $(this).attr("data-there-can-be-only-one-within") == theDataValue;
2837		}).val('');
2838		$(this).closest(theDataValue).find(":input[data-there-can-be-only-one-within,type='checkbox']").not(this).filter(function() {
2839			return $(this).attr("data-there-can-be-only-one-within") == theDataValue;
2840		}).attr("checked", false);
2841	});
2842
2843	// Don't bind crop images with livequery because the plugin clones the source img object, causing a loop
2844	$("img.crop-image").each(function() {
2845
2846		// Setup the minimum width and height values for the selection box
2847		var minWidth = $(this).attr("data-min-width") == null ? 500 : parseInt($(this).attr("data-min-width"));
2848		var minHeight = $(this).attr("data-min-height") == null ? 500 : parseInt($(this).attr("data-min-height"));
2849
2850		// Bind the crop functionality to the image
2851		var img = this;
2852		$.Jcrop(this, {
2853			allowSelect: false,
2854			minSize: [minWidth,minHeight],
2855			setSelect: [25,25,minWidth-25,minHeight-25],
2856			onChange: setCoords,
2857			onSelect: setCoords
2858		});
2859
2860		// Function to set the coordinates in the surrounding form, if present
2861		function setCoords(coords) {
2862			if ($(img).closest("form").length > 0) {
2863				var theForm = $(img).closest("form");
2864				$("input#field-crop-coords-x", theForm).val(coords.x);
2865				$("input#field-crop-coords-y", theForm).val(coords.y);
2866				$("input#field-crop-coords-width", theForm).val(coords.w);
2867				$("input#field-crop-coords-height", theForm).val(coords.h);
2868			}
2869		}
2870	});
2871
2872	// Load a specified image file into the facebox
2873	$("a.facebox-image-preview").livequery(function() {
2874		$(this).click(function(event) {
2875			$.facebox({image: $(this).attr("rel")});
2876			return stopEvent(event);
2877		});
2878	});
2879
2880	// Collapse divs in a tree structure
2881	$("div.collapsible").livequery(function() {
2882		var div = this;
2883		$("a.collapse-all", this).click(function(e) {
2884			$("li > ul", div).hide();
2885			$("a.expand-list", div).html("+");
2886			return stopEvent(e);
2887		});
2888		$("a.expand-all", this).click(function(e) {
2889			$("li > ul", div).show();
2890			$("a.expand-list", div).html("-");
2891			return stopEvent(e);
2892		});
2893		$("li > ul", div).hide();
2894		$("li", this).css("list-style", "none").each(function() {
2895			var li = this;
2896			if ($("> ul", li).length > 0) {
2897				$(this).prepend($("<a href='#' class='expand-list'>+</a>").click(function(e){
2898					var ul = $("> ul", li);
2899					if (ul.is(":visible")) {
2900						$(this).html("+");
2901						$(ul).hide();
2902					} else {
2903						$(this).html("-");
2904						$(ul).show();
2905					}
2906					return stopEvent(e);
2907				}));
2908			} else {
2909				if ($(li).siblings().find("ul").length > 0)
2910					$(li).prepend("<span class='expand-list'>&nbsp;</span>");
2911			}
2912		});
2913	});
2914
2915	// Bind date-span inputs to request the new dates from the server and change the fields in the form
2916	$("form select.date-span").livequery(function() {
2917
2918		// store the select object so the callback from the post can use it
2919		var timeSpanSel = this;
2920
2921		$(timeSpanSel).change(function(event) {
2922			if ($(timeSpanSel).val() != "EMPTY") {
2923
2924				// use Coldfusion to calculate our date range since Javascript doesn't have a built in function
2925				$.post("/home/ajaxCalculateDateRange", {dateSpan: $(timeSpanSel).val()}, function(data){
2926
2927					// set the values that Coldfusion returned
2928					$(timeSpanSel.form.startDate).val(data.STARTDATE);
2929					$(timeSpanSel.form.endDate).val(data.ENDDATE);
2930				}, "json");
2931			}
2932		});
2933
2934		// add events to startDate / endDate forms
2935		$(timeSpanSel.form.startDate).add(timeSpanSel.form.endDate)
2936
2937			// if they hit enter on the date fields to submit we want to blur the field
2938			.keypress(function(event) {
2939				var code = event.keyCode ? event.keyCode : event.which;
2940				if (code == 13) {
2941					$(this).blur();
2942					$(this.form).submit();
2943				}
2944			})
2945
2946			// on focus store the value
2947			.focus(function(event) {
2948				$(this).data('focusValue', $(this).val());
2949			})
2950
2951			// on change see if it changed
2952			.change(function(event){
2953
2954				// Validate the dates are in order. If not show an alert and set the one that was just changed to the other.
2955				var startDate = new Date(timeSpanSel.form.startDate.value), endDate = new Date(timeSpanSel.form.endDate.value);
2956				if (startDate > endDate) {
2957					createPageAlertBox(this, 'The start date must be before the end date.');
2958					$(this).val($(this).prop("name") == 'startDate' ? timeSpanSel.form.endDate.value : timeSpanSel.form.startDate.value).focus();
2959				}
2960
2961				if (timeSpanSel.form.startDate.value == '' && timeSpanSel.form.endDate.value == '' && $(timeSpanSel).find("option[val='all']").length > 0)
2962					$(timeSpanSel).val("all");
2963				else if ($(this).val() != $(this).data('focusValue'))
2964					$(timeSpanSel).val("EMPTY");
2965			})
2966
2967			// On blur, if they are the same, remove the page alert box.
2968			.blur(function() {
2969				var startDate = new Date(timeSpanSel.form.startDate.value), endDate = new Date(timeSpanSel.form.endDate.value);
2970				if (startDate <= endDate)
2971					removePageAlertBox();
2972			});
2973	});
2974
2975	// Enable master toggling of checkboxes in a listing column
2976	$("table th :input.toggle-checkboxes-in-column").livequery(function() {
2977		$(this).click(function(event) {
2978			var cellIndex = $(this).closest("th").index();
2979			var listingTable = $(this).closest("table");
2980
2981			if ($(this).attr("data-confirm") && !confirm($(this).data("confirm")))
2982				return stopEvent(event);
2983
2984			// Find the cells that correspond
2985			$("td:nth-child(" + (cellIndex + 1) + ") :input[type='checkbox']", listingTable).attr("checked", $(this).is(":checked")).change();
2986		});
2987	});
2988
2989	// Enable master toggling of checkboxes in a table
2990	$("table th :input.toggle-checkboxes-in-table").livequery(function() {
2991		$(this).click(function() {
2992			var listingTable = $(this).closest("table");
2993			// Find the cells that correspond
2994			$("td :input[type='checkbox'][checked='" + !$(this).is(":checked") + "']", listingTable).attr("checked", $(this).is(":checked")).change();
2995		});
2996	});
2997
2998	// Enable master toggling of checkboxes in a form
2999	$(":input.toggle-checkboxes-in-form").livequery(function() {
3000		$(this).click(function() {
3001			var form = $(this).closest("form");
3002			// Find the cells that correspond
3003			$(":input[type='checkbox'][checked='" + !$(this).is(":checked") + "']", form).attr("checked", $(this).is(":checked")).change();
3004		});
3005	});
3006
3007	// Handle preventing a user from leaving a page, unless we want them to (see form.clear-protect-user-data below), so they do not lose their work
3008	$("body.protect-user-data").each(function() {
3009		window.onbeforeunload = function() {return "Caution: Leaving this page may result in a loss of your work.";};
3010	});
3011
3012	$("form.clear-protect-user-data").on("submit", function() {
3013		window.onbeforeunload = null;
3014	});
3015
3016	// Bind post-to-on-change fields
3017	$("tr input[id^='field-post-to-on-change']").livequery(function() {
3018
3019		// If we told to update/submit only the column, then look for closest td else look for  entire row (tr)
3020		var theRow = $(this).is('[id^="field-post-to-on-change-column-only"]') ? $(this).closest("td") : $(this).closest("tr");
3021
3022		// Shorthand some variables
3023		var postField = this;
3024		var postAction = $(this).val();
3025
3026		// Decide on the ajax request type
3027		var postMethod = $(":input[id^='field-method']", theRow).length > 0 ? $(":input[id^='field-method']", theRow).val() : 'post';
3028		if (postMethod == 'put' || postMethod == 'delete')
3029			postMethod = "post";
3030
3031		// Bind any buttons or submits under the form that do not have .do-not-post to trigger change on click
3032		// We're doing this so it triggers the code down below.
3033		$(":input[type='button'],:input[type='submit'],a.save-row", theRow).click(function(event) {
3034
3035			// Show confirm dialog box if present
3036			if ($(this).attr("data-confirm") === undefined || confirm($(this).attr("data-confirm")))
3037				$(this).change();
3038
3039			return stopEvent(event);
3040		});
3041
3042		// Bind all inputs under the row to ajax submit the data as requested
3043		$(":input:not(.do-not-post),a.save-row", theRow).change(function() {
3044			var theField = this;
3045			var allowPost = true;
3046
3047			// Check for a prepost hook, and run it if present.
3048			if ($(theField).data("hooks.prepost") != null) {
3049				try {
3050					allowPost = eval($(theField).data("hooks.prepost"))(theRow, theField);
3051				} catch (e) {allowPost = false;}
3052			}
3053
3054			// Post if ok and form is validated?
3055			if (allowPost && validate_form(theRow)) {
3056				appendLoadingMask($(this).closest("td"));
3057				$.ajax({
3058					type: postMethod,
3059					url: postAction,
3060					data: $(":input", theRow).serialize(),
3061					success: function(){
3062						// Check for a post-post hook, and run it if present.
3063						if ($(theField).data("hooks.postpost") != null) {
3064							try {
3065								eval($(theField).data("hooks.postpost"))(theRow, theField);
3066							} catch (e) {}
3067						}
3068						$destroy($(theRow).find("div.loading-mask"));
3069						if ($(postField).data("clear-field") != null && $(postField).data("clear-field") == true)
3070							$(theField).val($(theField)[0].tagName == 'SELECT' ? 'EMPTY' : '');
3071					}
3072				});
3073			}
3074		});
3075
3076	});
3077
3078	/*Requires keyboard.js to function*/
3079	$("a[data-trigger-with]").livequery(function() {
3080		var theLink = this;
3081		bindKey($(this).data("trigger-with"), $(this).html(), null, function(event, keys, combo) {
3082			$(theLink).click();
3083		});
3084	});
3085
3086	// Fill-up/down links for inputs within listing columns need to fill up in the same column until they reach a non-empty value
3087	$("table tr td input.fill-up,table tr td input.fill-down,table tr td input.fill-both").livequery(function() {
3088		var input = this,
3089			tr = $(input).closest("tr"),
3090			prevTr = $(tr).prev(),
3091			nextTr = $(tr).next(),
3092			colIndex = $(input).closest("td").index(),
3093
3094			fillUp = $(input).is(".fill-up,.fill-both"),
3095			fillDown = $(input).is(".fill-down,.fill-both");
3096
3097		// Function to call on click of the images that triggers the fill in the appropriate direction
3098		var fill = function(direction) {
3099			var listing = $(input).closest("tbody");
3100			if (direction == 'up') {
3101				var endIndex = $("tr:lt(" + $(tr).index() + ")", listing).has("td:nth-child(" + (colIndex+1) + ") input:visible[value!='']").last().index();
3102				endIndex = endIndex == -1 ? 0 : endIndex;
3103				$("tr", listing).slice(endIndex, $(tr).index()).find("td:nth-child(" + (colIndex+1) + ") input:visible[value='']").val($(input).val()).change();
3104			} else {
3105				var endIndex = $("tr:gt(" + ($(tr).index() + 1) + ")", listing).has("td:nth-child(" + (colIndex+1) + ") input:visible[value!='']").first().index();
3106				endIndex = endIndex == -1 ? listing[0].children.length : endIndex;
3107				$("tr", listing).slice($(tr).index(), endIndex).find("td:nth-child(" + (colIndex+1) + ") input:visible[value='']").val($(input).val()).change();
3108			}
3109		};
3110
3111		// Function to add or remove the fill image links from the input
3112		var addOrRemoveFillLinks = function() {
3113			var val = $(input).val(),
3114				emptyBefore = val != '' && $($("td", prevTr)[colIndex]).find("input:visible[value='']").length == 1,
3115				emptyAfter = val != '' && $($("td", nextTr)[colIndex]).find("input:visible[value='']").length == 1,
3116
3117				// If this input isn't empty, and depending on whether it is fill up or down and there is an empty field in that direction, decide if the arrow should appear
3118				show = val != '' && ((fillUp && emptyBefore) || (fillDown && emptyAfter));
3119
3120			if (show) {
3121				// Float this input, and then add the fill div with links beside it.
3122				if (!$(input).hasClass("float-right"))
3123					$(input).addClass("float-right");
3124
3125				var div = $('<div class="fill-links"/>');
3126				if (fillUp && emptyBefore)
3127					$('<img src="/tpWheels/images/arrows/up.png" class="fill-up" />').click(function() {
3128						fill("up");
3129						$(this).remove();
3130					}).appendTo(div);
3131				if (fillDown && emptyAfter)
3132					$('<img src="/tpWheels/images/arrows/down.png" class="fill-down" />').click(function() {
3133						fill("down");
3134						$(this).remove();
3135					}).appendTo(div);
3136
3137				$(div).insertAfter(input);
3138			} else {
3139				$(input).closest("td").find("div.fill-links").remove();
3140			}
3141		};
3142
3143		// We may want to see the fill links on load
3144		addOrRemoveFillLinks();
3145
3146		// On change we need to decide on the fill links, as well.
3147		$(input).change(function() {
3148			addOrRemoveFillLinks();
3149		});
3150	});
3151
3152	// Moves the cursor to the next available visible input on the screen when the source input is filled up
3153	$(".move-when-full[maxlength]").livequery(function() {
3154		var maxLength = parseInt($(this).attr("maxlength"));
3155		var inputIndex = $(this).index(":input:visible");
3156		var moved = false;
3157
3158		// On key up, find the next visible input, ensuring that it's not full.
3159		$(this).keyup(function(event) {
3160			if ($(this).val().length == maxLength) {
3161				$(":input:visible:not(:disabled)").slice(inputIndex + 1).each(function() {
3162					if (!moved && ($(this).val() == '' || ($(this).attr("maxlength") != null && $(this).val().length < parseInt($(this).attr("maxlength"))))) {
3163						$(this).focus();
3164						moved = true;
3165					}
3166				});
3167			}
3168			moved = false;
3169		});
3170	});
3171
3172	// Strip any leading zeros on input
3173	$(":input.strip-leading-zeros").livequery(function() {
3174		$(this).change(function() {
3175			$(this).val($(this).val().replace(/^(0+)/g, ""));
3176		});
3177	});
3178
3179	// Enable copy inputs from a specified source to a specifed target
3180	$("[data-copy-from] :input.trigger-copy").on("change", function() {
3181		var target = $(this).closest("[data-copy-from]"),
3182			source = $($(target).data("copy-from")),
3183			isChecked = $(this).is(":checked");
3184
3185		// Loop over targetFields and copy data from sourceField
3186		$(":input:not('.do-not-copy')", target).each(function() {
3187			var isRadioOrCheckbox = $(this).is(":checkbox,:radio"),
3188				sourceFieldName = $(this).data("field-name") || $(this).attr('name'),
3189				sourceField = $(":input[data-field-name = '" + sourceFieldName + "']", source); // Look up sourceFeild by 'data-field-name';
3190
3191			// If we didn't find sourceField by 'data-field-name', try to find it by 'name'
3192			if (sourceField.length == 0)
3193				sourceField = $(":input[name = '" + sourceFieldName + "']", source);
3194
3195			// If we have more than one sourceFields, filter by value
3196			if (sourceField.length > 1)
3197				sourceField = $(sourceField).filter("[value='" + $(this).val() + "']");
3198
3199			if (sourceField.length > 0) {
3200				if (!$(this).hasClass("do-not-change")) {
3201					if (isChecked)
3202						isRadioOrCheckbox && $(sourceField).is(":checked") ? $(this).attr("checked", true).change() : $(this).val($(sourceField).val()).change();
3203					else
3204						isRadioOrCheckbox ? $(this).attr("checked", false).change() : $(this).val('').change();
3205				} else {
3206					if (isChecked)
3207						isRadioOrCheckbox && $(sourceField).is(":checked") ? $(this).attr("checked", true) : $(this).val($(sourceField).val());
3208					else
3209						isRadioOrCheckbox ? $(this).attr("checked", false) : $(this).val('');
3210				}
3211
3212				// If this is a autocomplete field trigger the lookup
3213				if ($(this).hasClass("autocomplete-hidden"))
3214					$(this).parent().find("input.autocomplete").data("lookup")($(this).val());
3215			}
3216			$(this).trigger("dataCopyFrom:after");
3217		});
3218	});
3219
3220	// Save and reset buttons should be enabled / disabled as necessary
3221	$("input.save-form-changes,input.reset-form-changes").livequery(function() {
3222		var theForm = this.form,
3223			saveFormChanges = $(this).hasClass("save-form-changes") ? this : $(theForm).find("input.save-form-changes"),
3224			resetFormChanges = $(this).hasClass("reset-form-changes") ? this : $(theForm).find("input.reset-form-changes");
3225
3226		$(theForm).change(function() {
3227			$(saveFormChanges).add(resetFormChanges).attr("disabled", false);
3228		});
3229
3230		$(resetFormChanges).click(function() {
3231			// If this is an additional fields form click all of the 'x's
3232			if ($(theForm).hasClass("additional-fields")) {
3233				// (the confirm is hard coded because we don't have a way to specify confirm on reset buttons.... not great)
3234				if (confirm("Are you sure you'd like to clear all filters?")) {
3235
3236					// Find the nearest dynamic listing and wipe out its query string
3237						$("#field-filter-query", "div.table-listing.dynamic:first").val('');
3238						$("form.filter-fields li.query", "div.table-listing.dynamic:first").remove();
3239
3240					// Force a clear on applicable fields
3241						$(".clear-field:visible", theForm).click();
3242						$(saveFormChanges).add(resetFormChanges).attr("disabled", true);
3243				}
3244			} else {
3245				theForm.reset();
3246
3247				// Force any autocomplete fields to update themselves after a reset
3248				$("input.autocomplete", theForm).each(function() {
3249					$(this).data("lookup")($(this).val());
3250				});
3251				$(saveFormChanges).add(resetFormChanges).attr("disabled", true);
3252			}
3253		});
3254	});
3255
3256	// Make entities with data-match-width-of do exactly what it sounds like
3257	$("[data-match-width-of]").livequery(function() {
3258		var theElement = this,
3259			elementToMatch = $($(this).data("match-width-of"));
3260
3261		if ($(elementToMatch).length > 0)
3262			$(theElement).css("min-width", $(elementToMatch).innerWidth());
3263	});
3264
3265	// Setup manual paging for listings when it is necessary
3266	$("div#query-paging").each(function() {
3267		var pageInput = $(this).find("input"),
3268			numPages = $(pageInput).data("numpages");
3269
3270		// Changing the page number field needs to refresh the page
3271		$(pageInput).change(function() {
3272
3273			// Get the new value and ensure it doesn't go beyond the max number of pages
3274			var val = $(this).val();
3275			if (val > numPages)
3276				val = numPages;
3277
3278			// Reload the page to the entered page number
3279			window.location.href = window.location.href.replace(/\?.*/, '') + '?page=' + val;
3280
3281		// On enter we want to trigger the change event
3282		}
3282).keydown(function(e) {
3283			var key = e.which ? e.which : e.keyCode;
3284			if (key == KEYENUM.RETURN) {
3285				$(this).change();
3286				return stopEvent(e);
3287			}
3288		});
3289	});
3290
3291	// On blur of trim fields... trim the data.
3292	$("input.trim").on("blur", function() {
3293		$(this).val(trim($(this).val()));
3294	});
3295
3296	// Show/Hide manual fax fields
3297	$("form.fax-form :input[name='fax[faxOption]']").livequery(function() {
3298		var theForm = $(this).closest("form"),
3299			manualFaxFields = $("ul.manual-fax-fields");
3300		$(this).change(function() {
3301			if ($(this).filter(":checked").val() == 'manual')
3302				$(manualFaxFields).show().find(":input").addClass("required-field");
3303			else
3304				$(manualFaxFields).hide().find(":input").removeClass("required-field");
3305		}).change();
3306	});
3307
3308	// Setup requireWhenElementIs fields
3309	$(":input[data-requiredwhenelementis]").livequery(function() {
3310		var theField = this,
3311
3312			// Find the label for this input
3313			theLabel = $(theField).closest("label"),
3314			theLabel = $(theLabel).length ? theLabel : $("label[for='" + $(theField).attr("id") + "']"),
3315
3316			// Separate different element fields by |
3317			bindData = $(theField).data("requiredwhenelementis").split("|");
3318
3319		// Loop over all elements
3320		for (i = 0; i < bindData.length; i++) {
3321			$(bindData[i].split(",")[0]).change(function() {
3322				var required = true;
3323				// Loop over all elements again to check what the value of each element is when any one changes.
3324				for (j = 0; j < bindData.length; j++) {
3325					var whenData = bindData[j].split(","),
3326						// The shift function removes the first element of an array and returns it.  This allows the following line to work properly
3327						whenField = whenData.length > 0 ? $(whenData.shift()) : null,
3328						whenValues = whenData.length > 0 ? whenData.toString().replace(/\(|\)/g, '').split(",") : null,
3329						// If an ! is the first value then the rest are negated
3330						negateWhenValues = whenValues.length > 0 && whenValues[0] == '!' ? true : false;
3331
3332					// If we have an element and values setup requirement toggling. If a requirement has already failed then we don't need to go through this condition
3333					if (whenField != null && whenValues != null && required) {
3334						var val = $(whenField).val();
3335
3336						if (negateWhenValues) {
3337							whenValues.shift();
3338							required = whenValues.indexOf($(whenField).val()) == -1;
3339						} else
3340							required = whenValues.indexOf($(whenField).val()) >= 0;
3341
3342						// If the controlling field is a checkbox and if 'whenValue' is 1 it is required on check and if 'whenValue' is 0 it is required on un-check
3343						if ($(whenField).prop("type").toLowerCase() == "checkbox")
3344							required = ((whenValues == 1 && $(whenField).is(":checked")) || (whenValues == 0 && !$(whenField).is(":checked")));
3345
3346						// If the controlling field is a radio button, find the checked field and compare that to the when values
3347						if ($(whenField).prop("type").toLowerCase() == "radio") {
3348							if (negateWhenValues) {
3349								required = whenValues.indexOf($(whenField).filter(":checked").val()) == -1;
3350							} else
3351								required = whenValues.indexOf($(whenField).filter(":checked").val()) >= 0;
3352						}
3353					}
3354				}
3355				$(theField).removeClass("required-field");
3356				if (required) {
3357					$(theField).addClass("required-field");
3358					if (theLabel != null && $(theLabel).find("em").length == 0)
3359						$(theLabel).append("<em>*</em>");
3360				} else {
3361					if (theLabel != null)
3362						$(theLabel).find("em").remove();
3363				}
3364			}).change();
3365		}
3366	});
3367
3368	// Setup disabledWhenElementIs fields
3369	$(":input[data-disabledwhenelementis]").livequery(function() {
3370		var theField = this,
3371
3372			// Shift removes the first element and returns it. The array is modified which allows the next line to work.
3373			bindData = $(theField).data("disabledwhenelementis").split(","),
3374			whenField = bindData.length > 0 ? $(bindData.shift()) : null,
3375			whenValues = bindData.length > 0 ? bindData.toString().replace(/\(|\)/g, '').split(",") : null;
3376
3377		// If we have an element and values setup requirement toggling
3378		if (whenField != null && whenValues != null) {
3379			$(whenField).change(function() {
3380				var val = $(this).val(),
3381					setDisabled = whenValues.indexOf($(this).val()) >= 0;
3382
3383				// If the controlling field is a checkbox and if 'whenValue' is 1 it is disabled on check and if 'whenValue' is 0 it is disabled on un-check
3384				if ($(whenField).prop("type").toLowerCase() == "checkbox")
3385					setDisabled = ((whenValues == 1 && $(whenField).is(":checked")) || (whenValues == 0 && !$(whenField).is(":checked")));
3386
3387				if (setDisabled) {
3388					$(theField).prop("disabled", true);
3389				} else {
3390					$(theField).prop("disabled", false);
3391				}
3392			}).change();
3393		}
3394	});
3395
3396	// Setup hideWhenElementIs fields
3397	$("*[data-hiddenwhenelementis]").livequery(function() {
3398
3399		var theElement = this,
3400			theInputField = this;
3401
3402		if (!$(theInputField).is(":input"))
3403			theInputField = $(":input", theElement);
3404
3405		if (/\|\||&&/gi.test($(theElement).data("hiddenwhenelementis"))) {
3406			var bindData = $(theElement).data("hiddenwhenelementis").match(/.{1,}?(\|\||&&|$)/gi);
3407		} else {
3408			var bindData = [$(theElement).data("hiddenwhenelementis")];
3409		}
3410
3411		var multipleControlsOperation = "";
3412		if (bindData.length > 1) {
3413			multipleControlsOperation = bindData[0].substring(bindData[0].length - 2);
3414		}
3415
3416		var validWhenFields = [];
3417		for (var i = 0; i < bindData.length; i++) {
3418			bindData[i] = bindData[i].replace(/\|\||&&/gi, "");
3419
3420			var currentBindData = bindData[i].split(","),
3421				// Shift removes the first element and returns it. The array is modified which allows the next line to work.
3422				whenField = currentBindData.length > 0 ? $(currentBindData.shift()) : null,
3423				whenValues = currentBindData.length > 0 ? currentBindData.shift().toString().replace(/\(|\)/g, '').split("|") : null,
3424				clearFlag = currentBindData.length > 0 ? currentBindData.shift() : false,
3425				triggerChangesFlag = currentBindData.length > 0 ? (currentBindData == "true" ? true : false) : false;
3426
3427			// If we have an element and values, setup requirement toggling
3428			if (whenField != null && whenValues != null) {
3429				validWhenFields.push({field:whenField, values:whenValues, clearFlag:clearFlag, triggerChangesFlag:triggerChangesFlag});
3430			}
3431		}
3432
3433		for (var i = 0; i < validWhenFields.length; i++) {
3434			validWhenFields[i].field.on("change", function() {
3435				var setHidden = false;
3436				for (var j = 0; j < validWhenFields.length; j++) {
3437					var whenField = validWhenFields[j],
3438						val = whenField.field.val(),
3439						currentFieldEvaluation = whenField.values.indexOf(whenField.field.val()) >= 0,
3440						checkboxOrRadioFlag = whenField.field.prop("type").toLowerCase() == "checkbox" || whenField.field.prop("type").toLowerCase() == "radio";
3441
3442					// If the controlling field is a checkbox and if 'whenValue' is 1 it is disabled on check and if 'whenValue' is 0 it is disabled on un-check
3443					if (checkboxOrRadioFlag) {
3444						currentFieldEvaluation = ((whenField.values == 1 && whenField.field.is(":checked")) || (whenField.values == 0 && !whenField.field.is(":checked")));
3445					}
3446
3447					// This field is a positive test
3448					if (currentFieldEvaluation) {
3449						// If we do not have multiple fields to test or the operation is an OR, mark the element to hide and break the loop.
3450						if (multipleControlsOperation == "" || multipleControlsOperation == "||") {
3451							setHidden = true;
3452							break;
3453						// If we do have multiple fields to test and we keep getting here, all is good. If we are on the last element, mark it to hide and break the loop.
3454						} else if (multipleControlsOperation == "&&" && j == validWhenFields.length - 1) {
3455							setHidden = true;
3456							break;
3457						}
3458
3459					// Negative test and we are trying to test multiple fields. Mark the element to NOT hide and immediately break the loop.
3460					} else if (multipleControlsOperation == "&&") {
3461						setHidden = false;
3462						break;
3463					}
3464				}
3465
3466				if (setHidden) {
3467					if (validWhenFields[0].clearFlag && $(theInputField).is(":input")) {
3468						validWhenFields[0].theFieldCheckboxOrRadioFlag = $(theInputField).prop("type").toLowerCase() == "checkbox" || $(theInputField).prop("type").toLowerCase() == "radio";
3469						if (validWhenFields[0].theFieldCheckboxOrRadioFlag)
3470							$(theInputField).prop("checked", false);
3471						else
3472							$(theInputField).val("");
3473						if (validWhenFields[0].triggerChangesFlag)
3474							$(theInputField).trigger("change");
3475					}
3476
3477					$(theElement).removeClass("hidden").addClass("hidden");
3478
3479				} else {
3480					$(theElement).removeClass("hidden");
3481				}
3482			}).trigger("change");
3483		}
3484	});
3485
3486	// Validate form on button click
3487	$("input.input-button.validate-form").livequery(function() {
3488		$(this).click(function(event) {
3489			if (!validate_form($(this).closest("form")))
3490				return stopEvent(event);
3491		});
3492	});
3493
3494	// Add masked input plugin's .mask to input fields with data-mask specified
3495	$("input[data-mask]").livequery(function() {
3496		// Default definitions
3497			// '9': "[0-9]"
3498			// 'a': "[A-Za-z]"
3499			// '*': "[A-Za-z0-9]"
3500
3501		// Add extra definitions here
3502		$.mask.definitions['A']='[A-Z]';
3503		$.mask.definitions['P']='[a|A|p|P]';
3504
3505		// Settings for .mask
3506		if ($(this).attr("data-mask-placeholder"))
3507			$.mask.placeholder = $(this).data("mask-placeholder");
3508
3509		$(this).mask($(this).data("mask"));
3510	});
3511
3512});
3513
3514/* Global Functions */
3515	function appendLoadingMask(div) {
3516
3517		// calculate properties of div
3518		var height = $(div).height() + parseInt($(div).css('padding-top')) + parseInt($(div).css('padding-bottom'));
3519
3520		// ensure that the loading mask is positioned properly
3521		var containerPosition = $(div).css("position");
3522		if (containerPosition != 'relative')
3523			$(div).css('position', 'relative');
3524
3525		// create the loading mask, apply properties, and append it to the div
3526		var div2 = $("<div class='loading-mask'><img src='/tpWheels/images/spinners/default.gif' /></div>");
3527		div2.css({
3528			height: height + 'px',
3529			width: $(div).width() + parseInt($(div).css('padding-left')) + parseInt($(div).css('padding-right')) + 'px',
3530			lineHeight: height + 'px'
3531		})
3532		.bind("before:remove", function() {
3533			if (containerPosition != 'relative')
3534				$(div).css('position', containerPosition);
3535		})
3536		.appendTo(div);
3537	}
3538
3539	// Enable 'stacking' custom events on a specified element's DOM event
3540	// Example: Stack afterScroll:tableHeader and afterScroll:siteFooterBar to $(window).scroll()
3541	// eventStackPush(window, 'scroll', 'after', 'afterScroll:tableHeader') and eventStackPush(window, 'scroll', 'after', 'afterScroll:siteFooterBar')
3542	// TODO: Make before useful in some way shape or form
3543	function eventStackPush(selector, eventName, stepInChain, customEventName) {
3544		var element = $(selector);
3545		if (element != null) {
3546			// Load the current event stacks on this element
3547			var eventStacks = $(element).data("eventStacks");
3548
3549			// Ensure the element has an event stack structure setup on it
3550			if (eventStacks == null)
3551				eventStacks = {};
3552
3553			// Ensure this event has an array stack prepared
3554			if (eventStacks[eventName] == null)
3555				eventStacks[eventName] = {before:[], after:[]};
3556
3557			// Grab the current stack
3558			var currentEventStack = eventStacks[eventName][stepInChain];
3559
3560			// Push the new event into the stack if it's not yet there
3561			if ($.inArray(customEventName, currentEventStack) == -1)
3562				eventStacks[eventName][stepInChain][currentEventStack.length] = customEventName;
3563
3564			// Update the eventStacks on the element
3565			$(element).data("eventStacks", eventStacks);
3566		}
3567	}
3568
3569	// Execute the given event stack created using eventStackPush
3570	function executeEventStack(selector, eventName, stepInChain) {
3571		var element = $(selector),
3572			eventStacks = $(element).data("eventStacks"),
3573			stackedEvents = eventStacks != null ? eventStacks[eventName][stepInChain] : null;
3574		if (stackedEvents != null) {
3575			for (var i = 0; i < stackedEvents.length; i++) {
3576				$(element).trigger(stackedEvents[i]);
3577			}
3578		}
3579	}
3580
3581	// Fix the ids, names and labels of given elements using the provided index
3582	function fixDistinctElements(idElements, objectElements, index) {
3583
3584		// HACK: Destroy the datepicker before modifying the ids and names
3585		$("input.input-date", idElements).datepicker("destroy");
3586		$("input.input-date", objectElements).datepicker("destroy");
3587		$("input.input-datetime", idElements).datetimepicker("destroy");
3588		$("input.input-datetime", objectElements).datetimepicker("destroy");
3589
3590		// Append a number to elements with an id already
3591		$(idElements).each(function() {
3592			var id = $(this).attr("id");
3593			$(this).attr("id", id + '-' + index);
3594			$("label[for='" + id + "']").attr("for", id + '-' + index);
3595		});
3596
3597		// Make the objects on inputs (with an object) uniquely named (requires extra processing on server side to find them...)
3598		$(objectElements).filter("[name*='[']").each(function() {
3599			var origName = $(this).attr("name"),
3600				name = $(this).attr("name").split("[")[0],
3601				regex = new RegExp("^" + name);
3602			$(this).attr("name", origName.replace(regex, name + index));
3603		});
3604
3605		// HACK: Rebind datepicker on 1ms delay to not cross up DOM references
3606		setTimeout(function() {
3607			bindDatePicker($("input.input-date", idElements));
3608			bindDatePicker($("input.input-date", objectElements));
3609			bindDatetimePicker($("input.input-datetime", idElements));
3610			bindDatetimePicker($("input.input-datetime", objectElements));
3611		}, 1);
3612	}
3613
3614	// Create a way to register a keyboard shortcut, name, and action for the application
3615	var key_bindings = new Array();
3616	function bindKey(combo, name, down, up) {
3617		var return_false = function(event, keys, combo) { return false; }
3618
3619		// default the key up/down callbacks to return alse
3620		down = down || return_false;
3621		up = up || return_false;
3622
3623		// simply call KeyboardJS to register the key(s)
3624		KeyboardJS.on(combo, down, up);
3625
3626		// format the keyboard combination for the legend
3627		combo = combo.toUpperCase()
3628			.replace(/(ctrl|alt|shift)/gi, "&lt;$1&gt;")
3629			.replace(/\+/g, " + ")
3630			.replace(/\s{2,}/g, " ");
3631
3632		// add the combination and name to the global key binding list
3633		key_bindings.push({combo: combo, name: name});
3634	}
3635
3636	// Creates an absolutely positioned box next to a specific element
3637	createPageAlertBox = function(element, message) {
3638		removePageAlertBox();
3639		$('<div id="alert-box-per-page" class="alert-box"><p>' + message + '</p>')
3640			.css({position: "absolute", top: $(element).offset().top - $(element).outerHeight(), left: $(element).offset().left + $(element).outerWidth()}).appendTo(document.body);
3641	};
3642
3643	// Removes the box created above. Called within the create just in case.
3644	removePageAlertBox = function() {
3645		$("#alert-box-per-page").remove();
3646	};

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.